subsequence = 非连续,即是否能够从数组中选出若干数字,使得这些数字之和 m o d m = 0 mod\ m=0 mod m=0。
本题的进阶版可见:代码源每日一题-选数(需标明选出符合条件的数字下标)
由于 n n n 的数据范围很大,可以想象直接使用 d p dp dp 必定会炸,但发现当 n ≥ m n ≥ m n≥m 时可根据鸽巢定理判定答案一定有解。
原因:求前缀和数组并对 m m m 取模,得到的结果必定落在 [ 0 , m − 1 ] [0,m-1] [0,m−1] 区间,共 m m m 个数。当 n ≥ m n ≥ m n≥m 时,必定将出现以下两种情况之一:①存在取模后为 0 0 0 的前缀和;②存在取模后相等的前缀和。对于第二种情况, [ a [ l + 1 ] , a [ r ] ] [\ a[l+1],a[r]\ ] [ a[l+1],a[r] ] 间的数字之和即满足要求。由此将 n n n 的数据范围降至 1000 1000 1000 ,可用 d p dp dp 进行求解。
二维dp
以 f [ i ] [ j ] f[i][j] f[i][j] 表示是否能够从前 i i i 个数取出若干数字使得其之和 m o d m = j mod\ m=j mod m=j ,则可推出状态转移方程。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1010;
int f[N][N],a[N];
signed main(){
int n,m;
cin>>n>>m;
if(n>=m){
cout<<"YES"<<endl;
return 0;
}
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]%=m;
}
for(int i=1;i<=n;i++){
f[i][a[i]]=1;
for(int j=0;j<m;j++)
if(f[i-1][j]){
f[i][j]=f[i-1][j];
f[i][(j+a[i])%m]=1;
}
}
if(f[n][0])
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
return 0;
}
一维滚动数组
以 t [ i ] t[i] t[i] 标记是否存在重复贡献。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1010;
int f[N],a[N],t[N];
signed main(){
int n,m;
cin>>n>>m;
if(n>=m){
cout<<"YES"<<endl;
return 0;
}
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]%=m;
}
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++)
t[j]=f[j];
for(int j=0;j<m;j++)
if(f[j]>=1&&t[j]!=0)
f[(j+a[i])%m]++;
}
if(f[0]>1)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
return 0;
}