http://acm.hdu.edu.cn/showproblem.php?pid=4669
思路:我们首先可以先将环变成一条链,比如原来是 9 6 4 2 8,变成一条链后是 9 6 4 2 8 9 6 4 2,然后设原环有n个珠子,设dp[i][j]表示以第i个珠子结尾,向前最多包含n个珠子,组成的数模k余j的数量。(这里的第i个是指的链中的第i个,我们从第n个开始),比如对于样例,以8结尾的就包括5种情况 8,28,428,6428,96428,五种情况,以9结尾的就有9,89,289,4289,64289,五种情况,其他易推之,我们首先暴力将以8结尾的所有情况O(n)地求出来,(这个太简单了就不说了),然后考虑怎么快速地转移到以9为结尾的方案,现在我们知道了以8结尾的所有状态,为了方便叙述,这里用V8代替以8结尾的方案,V9代替以9为结尾的方案。
易知,对于V8,除了96428这种情况,其他(n-1)中情况均可以直接转移个V9,即8->89,28->289,428->4289,6428->64289,那么对于这n-1中情况我们可以按模k分成k种情况,以O(k)的复杂度转移,即dp[i-1][j]->dp[i][(j*10+9)%k],最后还要减去96428这种情况,然后加上单独的9组成的情况,则我们完成了转移,最后得到V9。
如果您已经理解了前面的例子,那么这道题您已经做出了90%了,那还有10%是什么呢?
这里要注意的是珠子的权值的范围是1到1000,所以我们在做这一步转移的时候(dp[i-1][j]->dp[i][(j*10+9)%k])不能乘10,而是要乘以10^len,这里的len是指第i个数的十进制位数,这个可以通过与处理得到。
另外,由于我们转移的时候需要的信息仅仅是前一位的信息,所以我们不用设置dp[50000][200]这么大的空间,而是可以采用滚动数组的思想,交换使用空间,可以节省大量空间。
以上是整体思路,代码实现还有一些小的细节,具体实现可以参考下面的代码。
#include <string.h> #include <stdio.h> #include <algorithm> #define maxn 50010 #define ll long long using namespace std; int getlen(int x) { int sum=1; while(x/10) { sum++; x/=10; } return sum; } int pow[200010]; int a[maxn],len[maxn]; int cout[2][210]; void init(int n,int mod) { int i;pow[0]=1; for(i=1;i<=n;i++) pow[i]=(pow[i-1]*10)%mod; } int main() { // freopen("dd.txt","r",stdin); int n,k; while(scanf("%d%d",&n,&k)!=EOF) { int i; memset(cout,0,sizeof(cout)); init(n*4,k); for(i=0;i<n;i++) { scanf("%d",&a[i]); len[i]=getlen(a[i]); } a[n]=a[0]; len[n]=len[0]; ll ans=0; int le=0,sum=0; for(i=n;i>0;i--) { sum=(a[i]*pow[le]+sum)%k; cout[0][sum]++; le+=len[i]; } ans+=cout[0][0]; int t=1; for(i=1;i<n;i++) { memset(cout[t],0,sizeof(cout[t])); for(int j=0;j<k;j++) { cout[t][(j*pow[len[i]]+a[i])%k]+=cout[1-t][j]; } sum=(sum*pow[len[i]]+a[i])%k; cout[t][sum]--; cout[t][a[i]%k]++; ans+=cout[t][0]; t=1-t; sum=((sum-a[i]*pow[le])%k+k)%k; } printf("%I64d\n",ans); } return 0; }