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 ] dp[i][j] dp[i][j]表示以 i i i个数为结尾的所有子串中是否存在和模 m m m为 j j j的子串。
状态转移方程
d p [ i + 1 ] [ ( j + a [ i ] ) m o d m ] = d p [ i ] [ j ] dp[i+1][(j+a[i])\bmod m] =dp[i][j] dp[i+1][(j+a[i])modm]=dp[i][j]
- 代码一
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 ] pre[i]=\sum_{j=0}^{i-1}a[i]\\ \sum_{k=i}^j a[i] =pre[j+1] -pre[i] pre[i]=j=0∑i−1a[i]k=i∑ja[i]=pre[j+1]−pre[i]
其实是利用了同余的性质,如果存在区间 [ i , j ] [i,j] [i,j]满足其和是 m m m的倍数,
那么一定有 p r e [ j + 1 ] − p r e [ i ] = k m pre[j+1] -pre[i]= km pre[j+1]−pre[i]=km,
因此 p r e [ j + 1 ] ≡ p r e [ i ] ( m o d m ) pre[j+1] \equiv pre[i]\quad (\ \bmod \ m) pre[j+1]≡pre[i]( mod m)。
我们用哈希表记录下 p r e [ k ] m o d m pre[k] \bmod m pre[k]modm的值,如果后续出现了的相同的值 p r e [ k ′ ] m o d m pre[k'] \bmod m pre[k′]modm。那么说明区间存在。
利用同余性质将找区间转换成了,找相同的模值。
p r e [ k ] m o d m = p r e [ k ′ ] m o d m pre[k] \bmod m =pre[k'] \bmod m pre[k]modm=pre[k′]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)