1. 题意
题目描述
小明玩一个游戏:
系统发 1+n 张牌,每张牌上有一个整数。
第一张给小明(数字为 m);
后 n 张按发牌顺序排成连续一行。
需要判断:后 n 张牌中,是否存在连续的若干张牌,其和能整除小明手中牌的数字 m。
输入输出
输入
数据有多组,每组占两行,输入至文件结尾:
第一行:两个整数 n 和 m(空格分隔,m 是小明手中牌的数字);
第二行:n 个整数(空格分隔,代表后 n 张牌的数字)。
输出
对每组输入,若存在符合条件的连续子数组,输出 1;否则输出 0。
备注
数据范围:
1 ≤ n ≤ 1000;
牌上的整数 ≤ 400000;
输入组数 ≤ 1000;
输入格式保证正确,无需处理非法情况。
2. 题解
2.1 动态规划
自己想到的解法。定义 d p i j dpij dpij表示以 i i i个数为结尾的所有子串中是否存在和模 m m m为 j j j的子串。
状态转移方程
d p i + 1 ( j + a \[ i ) m o d m ] = d p i j dpi+1(j+a\[i)\bmod m] =dpij dpi+1(j+a\[i)modm]=dpij
- 代码一
cpp
int n,m;
int ans;
int dp[1001][1001];
void solve1_1()
{
memset(dp, 0, sizeof(dp));
ans = 0;
vector<int> a( n + 1, 0 );
for (int i = 1;i <= n; ++i) {
cin >> a[i];
}
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
int vmod = a[i] % m;
dp[i][vmod] = 1;
for (int j = 1; j < m; ++j) {
if (dp[i - 1][j]) {
dp[i][ (vmod + j) % m] = dp[i - 1][j];
}
}
if (dp[i][0]) {
ans = 1;
break;
}
}
}
时间复杂度 O ( n m ) O(nm) O(nm), 空间复杂度 O ( n m ) O(nm) O(nm)
由于我们只需要知道上一个状态,因此可以优化空间复杂度
- 代码二
cpp
void solve1_2()
{
vector<int> dp(m,0);
vector<int> ndp(m, 0);
ans = 0;
vector<int> a( n + 1, 0 );
for (int i = 1;i <= n; ++i) {
cin >> a[i];
}
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
int vmod = a[i] % m;
ndp[vmod] = 1;
for (int j = 0; j < m; ++j) {
if (dp[j]) {
ndp[(j + vmod) % m] = dp[j];
}
}
dp = ndp;
if (dp[0]) {
ans = 1;
break;
}
}
}
时间复杂度 O ( n m ) O(nm) O(nm), 空间复杂度 O ( m ) O(m) O(m)
2.2 前缀和
其实看了前缀和,也没有想到哈希表继续优化。
因此一开始只是写了前缀和加暴力枚举子串。
- 代码三
cpp
void solve2_1()
{
ans = 0;
vector<int> a(n, 0);
vector<int> pre(n + 1, 0);
int sum = 0;
for (int i = 0;i < n; ++i) {
cin >> a[ i ];
sum = ( sum + a[i] ) % m;
pre[ i + 1] = sum;
}
for (int i = 0; i < n; ++i) {
for (int j = i + 1;j <= n; ++j) {
int dif = (pre[j] + m - pre[i]) % m ;
if ( 0 == dif) {
ans = 1;
break;
}
}
if (ans) break;
}
}
这道题目找到的最优解,是前缀和加哈希表。
我们定义前缀和pre
p r e i = ∑ j = 0 i − 1 a i ∑ k = i j a i = p r e j + 1 − p r e i prei=\sum_{j=0}^{i-1}ai\\ \sum_{k=i}^j ai =prej+1 -prei prei=j=0∑i−1aik=i∑jai=prej+1−prei
其实是利用了同余的性质,如果存在区间 i , j i,j i,j满足其和是 m m m的倍数,
那么一定有 p r e j + 1 − p r e i = k m prej+1 -prei= km prej+1−prei=km,
因此 p r e j + 1 ≡ p r e i ( m o d m ) prej+1 \equiv prei\quad (\ \bmod \ m) prej+1≡prei( mod m)。
我们用哈希表记录下 p r e k m o d m prek \bmod m prekmodm的值,如果后续出现了的相同的值 p r e k ′ m o d m prek' \bmod m prek′modm。那么说明区间存在。
利用同余性质将找区间转换成了,找相同的模值。
p r e k m o d m = p r e k ′ m o d m prek \bmod m =prek' \bmod m prekmodm=prek′modm
- 代码四
cpp
void solve2_2()
{
vector<int> a(n, 0);
// vector<int> pre(n + 1, 0);
int sum = 0;
unordered_set<int> pos;
pos.insert(0);
for (int i = 0;i < n; ++i) {
cin >> a[ i ];
sum = ( sum + a[i] ) % m;
if ( pos.count(sum)) {
ans = 1;
}
else {
pos.insert( sum );
}
}
}
时间复杂度 O ( n ) O(n) O(n), 空间复杂度 O ( m ) O(m) O(m)