引:这道题前面是两道黑题,我看了题解都畏写,只好先写此题了。

  • 题意: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.做题前三思