转化题意:
给你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; }