转化题意:
给你n个数,问你是否能选出若干个数使得数字的和为3600的倍数(至少选一个)
一开始,我打的01背包,dp[i]表示和模3600为i的方案数
一开始,dp[0]为1,不难发现,若dp完后,dp[0]>1的话,就表示存在和为3600,不过,我们计算下复杂度:
很明显,对于此题是过不了的,所以我们需要优化
注意到,我们仅需知道dp[0]是否大于1而并不需要具体的值,所以,我们可以在dp中途判断下,dp[0]是否大于1,若是的话就推出即可,这样对于答案是Yes的情况,可以起到很大的优化,那么,对于No呢?很明显,是没法进行优化的,那么,我们考虑下该怎么做。。。(当然,你写个这个此题就可以直接过了)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1,mod=3600;
int dp[2][3600];
int a[N];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i]%=mod;
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;++i){
if(dp[0][0]>1){
break;
}
for(int j=0;j<3600;++j){
int now=((j-a[i])%mod+mod)%mod;
dp[1][j]+=dp[0][now];
}
for(int j=0;j<=3600;++j){
dp[0][j]+=dp[1][j];
dp[1][j]=0;
}
}
if(dp[0][0]>1){
puts("YES");
}else{
puts("NO");
}
}
return 0;
} 注意到,我们的dp[i]表示的是模3600为i的方案数,那么,我们其实对于所有a[i],我们可以将它们根据模3600的结果分成3600组,现在,我们题目就转化为了,从3600组中,每组取若干个数,使得和模3600为0,而由于每组内的数值相同,我们相当于,题目转化为,有个1,
个2,
个3...
个3599,选出模为0,(要是
>0的话,直接输出YES就行了。。。)这个很明显,做一个二进制拆分即可~,然后对于复杂度中的"n"减到很小的程度,然后亲测,跑 @SemonChan 大佬给的hack数据,用时300ms ~
当然,我们还可以发现,二进制拆分后,我们又相当于是做同一道题,只是n值变小了,那么,我们还可以重复几次二进制拆分,23333
代码:(一次二进制拆分)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1,mod=3600;
int dp[2][3600],tot[mod];
int a[N];
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(tot,0,sizeof(tot));
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i]%=mod;
++tot[a[i]];
}
if(tot[0]){
puts("YES");
continue;
}
n=0;
for(int i=1;i<3600;++i){
int now=1;
while(tot[i]>=now){
a[++n]=(now*i)%mod;
tot[i]-=now;
now<<=1;
}
if(tot[i]){
a[++n]=(tot[i]*i)%mod;
}
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;++i){
if(dp[0][0]>1){
break;
}
for(int j=0;j<3600;++j){
int now=((j-a[i])%mod+mod)%mod;
dp[1][j]+=dp[0][now];
}
for(int j=0;j<=3600;++j){
dp[0][j]+=dp[1][j];
dp[1][j]=0;
}
}
if(dp[0][0]>1){
puts("YES");
}else{
puts("NO");
}
}
return 0;
} 
京公网安备 11010502036488号