引:这道题前面是两道黑题,我看了题解都畏写,只好先写此题了。
- 题意:P2481
- 思路:
这题的思路很有意思:我们可以把数转化一些111……11的和。(暂且叫这种数11数)
然而11数的长度n(<=1e18).怎么办呢 >_^
我们发现是%p意义下的,p(<=500)
因此存\(g[i]\)表示11数%p为i的个数。
所以我们要求\(g[i]\)下标i的和%p为0的方案数
而且11数%p是肯定会有循环节,而且长度<=p的,所以复杂度是\(O(p)\)而不是\(O(n)\)
因此状态为:\(f[i][j][z]\):下标为0~i,选了j个下标,%p和为z的方案数。
再枚举当前选k个i,而可以选相同的11数相加
\(f[i+1][j+k][(z+k*i)\ mod\ p]=f[i][j][k]*C_{g[i]+k-1}^k\)
然后我就这样写了,样例都没过……
因为如果\(g[i]=0\),\(g[i]+k-1<k\)而此时表示不选应该直接传递回上一次的值
而且改了又发现C第一维1e18并不用传统方法,我们又没必要添加个log,后来发现可以手推每个\(c[i][k]\)表示\(c[g[i]+k-1][k]\)
然后查到了一个细节错误后就A了! - 代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int g[505];
ll inv[505],mod=999911659,dp[505][11][505],flag[505],c[505][505]; //[Ç°i¸ö][Ñ¡j¸ö][ϱêºÍÄ£pµÄÖµ]:·½°¸Êý
ll n,p,kk;
void init() {
ll k=0,m=0,l=0;
if(n<p) {
for(int i=1;i<=n;i++) {
k=(k*10+1)%p,g[k]++;
}
kk=k;
}
else {
for(int i=1;i<=n;i++) {
k=(k*10+1)%p;
if(flag[k]) {
l=flag[k],m=i-l; break;
}
g[k]++; flag[k]=i;
}
if(!m) return;
for(int i=0;i<p;i++) {
if(flag[i]>=l) {
g[i]+=(n-flag[i])/m%mod;
if((n-flag[i])%m==0) kk=i;
}
}
}
}
void solve() {
inv[1]=1;
for(int i=2;i<9;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
for(int i=0;i<p;i++) {
c[i][0]=1;
if(!g[i]) continue;
for(int k=1;k<9;k++,g[i]=(g[i]+1)%mod) {
c[i][k]=c[i][k-1]*g[i]%mod*inv[k]%mod;
}
}
dp[0][0][kk]=1;
for(int i=0;i<p;i++) {
for(int j=0;j<9;j++) {
for(int z=0;z<p;z++) {
for(int k=0;k<9-j;k++) {
dp[i+1][j+k][(z+k*i)%p]+=dp[i][j][z]*c[i][k]%mod;
}
}
}
}
}
int main() {
// freopen("ll.out","w",stdout);
scanf("%lld%lld",&n,&p);
init();
solve();
ll ans=0;
for(int i=0;i<9;i++) {
ans=(ans+dp[p][i][0])%mod;
}
printf("%lld",ans);
return 0;
}
ps.做题前三思