[BZOJ1974][SDOI2010]代码拍卖会[插板法]

题意

询问有多少个数位为 \(n\) 的形如 \(11223333444589\) 的数位值不下降的数字在\(\mod p\) 的意义下同余 \(0\)

$n\leq 10^{18}\ ,p\leq 500 $ 。

分析

  • 考虑普通的状态,矩乘和考虑每种数字选择什么都没法做,要另辟蹊径。

  • 发现这样的数字都可以拆分成1~9个形如 \(111111\) 的形式,记为 \(\rm gg\)

  • 考虑算出所有此类数字在\(\mod p\) 意义下余数为 \(x\) 的有多少个。

  • 状态呼之欲出: \(f_{i,j,k}\) 表示考虑到 \(\rm gg\) 余数为 \(i\) 的 ,总的余数为 \(j\) ,已经选择了 \(k\)\(\rm gg\) 的方案总数。

  • 转移枚举 \(\rm gg\) 余数为 \(i\) 的选择了多少个,注意这类 \(\rm gg\) 的选择是组合而不是排列,考虑插板法算方案。

  • 总时间复杂度为\(O(10^2*p^2)\)

可重集的排列变组合可以考虑插板法。

代码

#include<bits/stdc++.h>
using namespace std;
#define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].last,v=e[i].to)
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pb push_back
typedef long long LL;
inline int gi(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch))	{if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
	return x*f;
}
template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;}
template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;}
const int N=504,mod=999911659;
LL st,n,p,rev[N],cnt[N],f[N][N][10],inv[N];
int pos[N];
void add(LL &a,LL b){a+=b;if(a>=mod) a-=mod;}
LL C(LL n,LL m){
	LL res=1ll;
	for(LL i=n-m+1;i<=n;++i) res=i%mod*res%mod;
	for(LL i=2;i<=m;++i) res=res*inv[i]%mod;
	return res;
}
int main(){
	scanf("%lld%lld",&n,&p);
	inv[1]=1;
	for(int i=2;i<=500;++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	memset(pos,-1,sizeof pos);
	pos[0]=0,rev[0]=0,cnt[0]=1;LL v=1%p;
	for(LL i=1;i<=min(n,p);++i){
		if(pos[v]!=-1){
			LL len=i-pos[v],a=(n-i+1)/len,b=(n-i+1)%len;
			st=rev[pos[v]+(b-1+len)%len];
			for(int j=pos[v];j<i;++j) cnt[rev[j]]+=a+(j-pos[v]+1<=b);
			break;
		}else if(i==n) st=v;
		pos[v]=i,rev[i]=v,cnt[v]++;
		v=(v*10+1)%p;
	}
	rep(k,0,8) f[0][st][k]=C(cnt[0]+k-1,k);
	
	rep(i,1,p-1)
	rep(j,0,p-1)
	rep(k,0,8){
		f[i][j][k]=f[i-1][j][k];
		rep(h,1,k)
		add(f[i][j][k],f[i-1][((j-h*i)%p+p)%p][k-h]*C(cnt[i]+h-1,h)%mod);
	}
	printf("%lld\n",f[p-1][0][8]);
	return 0;
}
posted @ 2018-10-19 18:28  fwat  阅读(197)  评论(0编辑  收藏  举报