洛谷 P2481 [SDOI2010]代码拍卖会(背包+隔板法)

题面传送门

题意:
给出 \(n,p\),求有多少 \(n\) 位数 \(X=a_1a_2a_3\dots a_n\) 满足:

  1. \(n\) 位数不含前导零
  2. \(a_i \leq a_{i+1}\)
  3. \(X\)\(p\) 的倍数。

答案对 \(998244353\) 取模。
\(1 \leq n \leq 10^{18}\)\(1 \leq p \leq 500\)

CSP 之前做的了,隔了好久才把题解补了。。。
本题的突破口在于怎样处理 \(a_i \leq a_{i+1}\) 这个条件。
我们不妨进行一个转化:每次加一个全是 \(1\) 的后缀,最多可以加 \(9\) 次,这样就能保证得到的数一定满足条件 2。
又由于 \(X\) 不能含前导零,所以我们加的后缀中必须有一个是长度 \(n\) 的后缀。
于是我们有了优秀的 \(n^8\) 的做法,可以拿到 10 分的好成绩。
但仔细观察就可以发现,每个后缀 \(111...11\)\(p\) 取模的余数呈周期分部。
例如当 \(p=12\) 的时候,各后缀模 \(p\) 的余数分别为:\(1,11,9,7,11,9,7,11,...\)
不难看出除了 \(1\) 以外每三个一循环。
有了这个发现,我们就可以将这 \(n\) 个后缀分为三类:

  1. 还没进循环节
  2. 在完整的循环节中
  3. 在最后多出的部分中

证明可用扩展欧拉定理。
\(cnt_m\) 为模 \(p\)\(m\) 的后缀数。分类讨论可以在 \(\mathcal O(p)\) 的时间内求出 \(cnt\)
接下来就可以 \(dp\) 了。\(dp[i][j][k]\) 表示选择了余数为 \(0\)\(i\) 的后缀共 \(j\),它们的和模 \(p\)\(k\) 的方案数。
转移的时候枚举选择多少个余数为 \(i\) 的后缀,记为 \(s\)
由于可以重复选择,这一部分的方案数可以用隔板法求,\(\dbinom{cnt_i+s-1}{s}\)
最后别忘了我们必须选择长度为 \(n\) 的后缀,所以答案为 \(\sum\limits_{i=0}^8dp[p-1][i][(p-pn)\mod m]\),其中 \(pn\) 为长度为 \(n\) 的后缀对 \(p\) 取模的结果。
别忘了特判 \(p=1\),卡了我很久。。。。。。
综上,这是一道非常不错的综合题。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define mp make_pair
#define int long long
typedef pair<int,int> pii;
typedef long long ll;
const ll MOD=999911659;
ll n;
int p,a[505];
int vis[505];
ll cnt[505];
ll dp[505][10][505];
inline ll qpow(ll x,ll e){
	ll ans=1;
	while(e){
		if(e&1) ans=ans*x%MOD;
		x=x*x%MOD;e>>=1;
	}
	return ans;
}
ll inv[505];
inline ll getc(ll x,ll y){
	ll ans=1;
	for(int i=x;i>=x-y+1;i--) ans=ans*(i%MOD)%MOD;
	for(int i=1;i<=y;i++) ans=ans*inv[i]%MOD;
	return ans;
}
inline void inc(ll &x,ll y){
	x+=y;if(x>=MOD) x-=MOD;
}
signed main(){
	scanf("%lld%lld",&n,&p);
	for(int i=1;i<=10;i++) inv[i]=qpow(i,MOD-2);
	int cur=1;vis[cur]=1%p;
	int len1,len2;a[1]=1%p;
	for(int i=2;i<=1000;i++){
		cur=(cur*10+1)%p;a[i]=cur;
		if(vis[cur]){
			len1=vis[cur]-1;len2=i-vis[cur];
			break;
		}
		vis[cur]=i;
	}
	if(n<=len1) for(int i=1;i<=n;i++) cnt[a[i]]++;
	else{
		ll cyc=(n-len1)/len2;
		ll rem=(n-len1)%len2;
		for(int i=1;i<=len1;i++) cnt[a[i]]++;
		for(int i=len1+1;i<=len1+len2;i++) cnt[a[i]]+=cyc;
		for(int i=len1+1;i<=len1+rem;i++) cnt[a[i]]++;
	}
	for(int i=0;i<9;i++){
		dp[0][i][0]=getc(cnt[0]+i-1,i)%MOD;
	}
	for(int i=0;i<p-1;i++) for(int j=0;j<9;j++) for(int k=0;k<p;k++){
		for(int l=0;l+j<9;l++){
			inc(dp[i+1][l+j][(k+l*(i+1))%p],dp[i][j][k]*getc(cnt[i+1]+l-1,l)%MOD);
		}
	}
	int gn;
	if(n<=len1) gn=a[n];
	else if((n-len1)%len2==0) gn=a[len1+len2];
	else gn=a[len1+(n-len1)%len2];
	ll ans=0;
	for(int i=0;i<9;i++) inc(ans,dp[p-1][i][(p-gn)%p]);
	printf("%lld\n",ans);
	return 0;
}
/*
1000000000 499
1000000000000000000 1
2 1
*/
posted @ 2020-11-16 22:06  tzc_wk  阅读(137)  评论(0)    收藏  举报