P2481 [SDOI2010] 代码拍卖会

题目大意

详细题目传送门
求有多少个 \(n\) 位数 \(v=\overline{a_1a_2\cdots a_n}\) 满足每一位都小于等于右边的下一位,且有给出 \(p\) 使 \(v|p\)

\(n\leq10^{18},p\leq500\)

思路

注意到 \(n\) 很大,但 \(p\) 很小,所以基本的数位 dp 就无法做到了。这时候注意到一个神仙的转换,每一个 \(v\) 都可以转换成不同位的全 \(1\) 构成。如 \(111222333=111111111+111111+111\)。似乎我们可以将原问题转化成考虑不同长度的动规。

发现 \(p\) 很小,所以我们把这些 \(1\) 都在模 \(p\) 意义下考虑即可。设 \(g_i\) 表示的是所有 \([1,n]\) 长度的串有几个 \(\bmod p=i\)。一会儿讲具体求法。

于是就有 \(f_{i,j}\) 表示的是当前考虑到了所有 \(\bmod p=i\) 的数,构成余数为 \(j\) 的方案数。但是发现会有重复,因为在更新时会有进位产生。

所以有状态 \(f_{i,j,k}\) 表示考虑到考虑到了所有 \(\bmod p=i\) 的数,构成余数为 \(j\) 的方案数,一共使用了 \(k\)\(1\) 的后缀。所以这样的话每一次考虑就最多有 \(9\) 个叠加,所以无法产生进位不会重复计算。得到转移公式:

\[f_{i+1,(j+o\cdot i)\bmod p,k+o}\stackrel{+}{\leftarrow} \binom{g_i+o-1}{o}\cdot f_{i,j,k} \]

这个转移式的意思就是 \(g_i\) 中选 \(o\) 个同类后缀的数量再乘原来的。

答案就是 \(\sum f_{p,0,i}\)。但是注意到不能有前导零,所有至少有一个 \(n\)\(1\) 来进行占位,所以 \(k,o\) 都不能枚举到 \(9\),只能从 \(0\)\(8\)

考虑 \(g\) 的求法。对于一个长度为 \(i\) 的后缀 \(f_i\) 在模 \(p\) 的情况下可以有转移 \(f_i=(f_{i-1}*10+1)\bmod p\),则发现如果出现一个之前已经出现过的 \(f_i\) 就说明出现了循环节(因为模的性质),且这个循环节会很短,所以每一个 \(g\) 我们只要看最初出现的位置同样在整个到 \(n\) 中有几个循环节包括了即可。

然后特别的我们要同时处理一下 \(f_n\) 的值,因为在初始化时我们预先设定了一个长度为 \(n\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const ll MAXP=500+5;
const ll MOD=999911659ll;
ll inv[10];
ll Cx(ll n,ll m){
    ll ans=1;
    for(ll i=n-m+1;i<=n;++i){
        ans*=i;
        ans%=MOD;
    }
    for(ll i=1;i<=m;++i){
        ans*=inv[i];
        ans%=MOD;
    }
    return ans;
}
ll C(ll n,ll m){
    if(m==0){
        return 1;
    }
    return Cx(n%MOD,m%MOD)*C(n/MOD,m/MOD)%MOD;
}
ll n,p;
ll g[MAXP];
ll dp[MAXP][9][MAXP];
ll vis[MAXP];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>p;
	inv[1]=1;
	for(int i=2;i<=9;++i){
	    inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	}
	ll ln=0;
	if(n<=p){
	    for(int i=1;i<=n;++i){
	        ln=(ln*10+1)%p;
	        g[ln]++;
	    }
	}else{
	    ll l,len;
	    ln=!(p==1);
	    for(int i=1;;++i){
	        if(vis[ln]){
	            l=vis[ln];
	            len=i-vis[ln];
	            break;
	        }
	        g[ln]++;
	        vis[ln]=i;
	        ln=(ln*10+1)%p;
	    }
	    for(int i=0;i<p;++i){
	        if(vis[i]>=l){
	            g[i]+=(n-vis[i])/len%MOD;
	            if((vis[i]-l+1)%len==(n-l+1)%len){
	                ln=i;
	            }
	        }
	    }
	}
	dp[0][0][ln]=1;
	for(int i=0;i<p;++i){
	    for(int j=0;j<9;++j){
	        for(int k=0;k<p;++k){
	            for(int ne=0;ne<9-j;++ne){
	                dp[i+1][j+ne][(k+ne*i)%p]+=C(g[i]+ne-1,ne)*dp[i][j][k]%MOD;
	                dp[i+1][j+ne][(k+ne*i)%p]%=MOD;
	            }
	        }
	    }
	}
	ll ans=0;
	for(int i=0;i<9;++i){
	    ans+=dp[p][i][0];
	    ans%=MOD;
	}
	cout<<ans<<endl;
	return 0;
}
posted @   tanghg  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示