[SDOI2010]代码拍卖会

一、题目

点此看题

二、解法

虽然一眼看上去是数位 d p dp dp,但这长度 1 e 18 1e18 1e18明显不可做。

还是要找规律,首先有一个我这辈子都想不到的转化,原来是数是形如 11122233.... 11122233.... 11122233....,可以转化成 11111111.... 11111111.... 11111111.... 00011111.... 00011111.... 00011111.... 00000011.... 00000011.... 00000011....的叠加,设 c n t [ i ] cnt[i] cnt[i]为长度 n n n以内的,模 p p p i i i的数个数。

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为前 i i i类数中(按余数分的类),选了 k k k个数叠加,叠加后模 p p p j j j的方案数,显然这个转移类似背包:
d p [ i + 1 ] [ j ] [ k ] = d p [ i ] [ j − l × i ] [ k − l ] × C c n t i + l − 1 l dp[i+1][j][k]=dp[i][j-l\times i][k-l]\times C_{cnt_i+l-1}^l dp[i+1][j][k]=dp[i][jl×i][kl]×Ccnti+l1l初始化 d p [ 0 ] [ s ] [ 0 ] = 1 dp[0][s][0]=1 dp[0][s][0]=1(必须选全 1 1 1,因为不能有前导零),重点解释一下最后那个组合数,因为每个数可以无限选,所以相当于解方程 x 1 + x 2 . . . = l x_1+x_2...=l x1+x2...=l,由于每个是非负数解所以可以用隔板法算出方案数为 C c n t i + l − 1 c n t i − 1 = C c n t i + l − 1 l C_{cnt_i+l-1}^{cnt_i-1}=C_{cnt_i+l-1}^l Ccnti+l1cnti1=Ccnti+l1l

现在问题在于如何算 c n t cnt cnt,由于计算方式是单一的(乘10加1),利用抽屉原理可以知道最多 p + 1 p+1 p+1次就可以出现循环节,所以找到循环节之后就可以算了。

#include <cstdio>
#include <cstring>
#define int long long
const int M = 505;
const int MOD = 999911659;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,s,p,ans,sum,beg,len,inv[M];
int cnt[M],pos[M],c[M][M],dp[M][M][10];
signed main()
{
	n=read();p=read();
	if(n<=p)
	{
		for(int i=1;i<=n;i++) sum=(sum*10+1)%p,++cnt[sum];
		s=sum;
	}
	else
	{
		for(int i=1;i<=p+1;i++)
		{
			sum=(sum*10+1)%p;
			if(cnt[sum])
			{
				beg=pos[sum];len=i-pos[sum];
				break;
			}
			++cnt[sum];pos[sum]=i;
		}
		for(int i=0;i<p;i++)
			if(cnt[i] && pos[i]>=beg)
			{
				cnt[i]=(n-beg+1)/len;
				if(pos[i]-beg+1<=(n-beg+1)%len) ++cnt[i];
				if((pos[i]-beg+1)%len==(n-beg+1)%len) s=i;
			}
	}
	inv[0]=inv[1]=1;
	for(int i=2;i<=8;i++)
		inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	for(int i=0;i<p;i++)
	{
		c[i][0]=1;
		if(cnt[i])
			for(int j=1;j<=8;j++)
			{
				c[i][j]=cnt[i]*c[i][j-1]%MOD*inv[j]%MOD;
				cnt[i]=(cnt[i]+1)%MOD;
			}
	}
	dp[0][s][0]=1;
	for(int i=0;i<p;i++)
		for(int j=0;j<p;j++)
			for(int k=0;k<9;k++)
				for(int l=0;l<=k;l++)
					dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][(j-l*i%p+p)%p][k-l]*c[i][l]%MOD)%MOD;
	for(int i=0;i<=8;i++)
		ans=(ans+dp[p][0][i])%MOD;
	printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值