题目大意:输入n,序列包含n个整数,问是否能够找到一个子序列的和为3600的整数倍
题目数据可以达到1e9,这是远超3600的,事实上我们也无需真的去开一个1e9的数组来存放这些数据,因为要求的只是能否使和成为3600的倍数,所以我们只需关注它们与3600的余数即可。
另外,当n>3600的时候,前3600个数的和就一定是3600的倍数,就一定可以,所以我们在这种情况下就无需判断了。
那么在n<3600时,具体要怎么操作呢?
我们可以建立一个bool型的dp数组,里面存放的是小于3600的所有正整数,它代表能否使和与3600的余数为i,那么,当dp【0】为1时,我们就可以说找到了。对于每次操作,外层遍历所有物品,内层遍历所有背包容量j,如果j的dp值为真,那么这时加上遍历到的物品的重量mas【i】,则
dp【(j+mas[i])%3600】=1;另外,dp[mas[i]%3600]也要等于1,考虑的就是现在只放一个mas【i】,因为它的重量可能就是3600的倍数,自然不能忽略。
另外,
for(int i=1;i<=n&&!dp[0];++i){
int cnt=0;
for(int j=1;j<3600;++j){
if(dp[j]){
v[cnt++]=(j+mas[i])%3600;
}
}
for(int j=0;j<cnt;++j){
dp[v[j]]=1;
}
dp[mas[i]%3600]=1;
}
该操作内部进行了多次取模处理,我们最好在存入数据时就把它们都对3600取模,这样后面就不会重复这么多次操作,可以节省时间。
同时你也会注意到,在这里我并没有像上面说的那样直接做 dp【(j+mas[i])%3600】=1,而是用了一个v数组来暂时存放余数,然后在小循环外部再把对应的余数对应的dp值改为真。
在代码注释中解释:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll mas[100010];
int dp[3700];
int n,t;
int v[10000];
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;++i){
cin>>mas[i];
//mas[i]%=3600;
}
if(n>3600){
cout<<"YES"<<endl;
continue;
}
memset(dp,0,sizeof dp);
for(int i=1;i<=n&&!dp[0];++i){
int cnt=0;
for(int j=1;j<3600;++j){
if(dp[j]){//比如你还在加某个数,你就把他把另外一个数的
//余数标记了,他很可能会和那个标记再次标记一次,比如dp[2]=1,
//你这次加1,然后把dp[3]加进去了,j变3的时候发现dp[3]=1
//,然后dp[4]=1,就这样停不下来了。
//问题出在判断上,后面的dp【j】也会等于0,就超时了?
//dp[(j+mas[i])%3600]=1;
v[cnt++]=(j+mas[i])%3600;
}
}
for(int j=0;j<cnt;++j){
dp[v[j]]=1;
}
dp[mas[i]%3600]=1;
}
if(dp[0]){
cout<<"YES"<<endl;
}
else{
cout<<"NO"<<endl;
}
}
return 0;
}