题面
分析
首先
n!
中p的指数是
∑∞i=0⌊npi⌋
(有一个p的有多少个,两个的,三个的…)
那么对于
Cmn
就是
在p进制下理解这个式子,发现任意第i项,后面一大堆不是0就是1.
当什么情况下是1 ? 就是p进制下m+(n-m)是否有进位。如果有进位,那么现在就没有取到这个进位(下取整就类似二进制下右移),那么就是1.
将m视为a,将b视为n-m,那么现在问题就是求
(a,b),满足a+b<=n
且a+b在p进制下有不少于k个进位的对数。
这其实就是库默尔定理。
考虑数位dp。 设
f[i][j][top][add]
表示第i位时进位数j,目前为止两数相加有没有卡上界,是否要求下一位要进位(这样才能预先计算进位贡献)
转移比较复杂,所以我直接枚举两个相关状态然后将所有不可能转移剔除。
这样时间++,但是代码好写很多。
注意到这样转移复杂度是p^2的,可以看出转移来转移去都是那几个状态在转移,那么其实系数是有规律的,可以直接计算。 大概就是
0(不卡上界),0(下一位不进位)->0,0,p1+p2< p,
0,0->0,1,p1+p2+1< p
0,1->0,0,p1+p2>=p
0,1->0,1,p1+p2+1>=p
1,0类似
这样的p1,p2对数可以直接计算之类的。
demo
80分。 O(n的p进制下位数∗p2∗w) ,w是一个随转移写法而定的常数,在我的程序里大概为16.
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mo=1e9+7,N=1e3+10,Mx=1e3,gmo=1e8;
char s[N];
ll np[N],p,k;
ll mi[N];
ll f[2][3000][2][2],o;
// 位数,进位数,卡上界,是否要求下一位进位
inline void add(ll &x,ll y) {x=(x+y)%mo;}
struct _int {
ll a[Mx];
ll& operator[](ll i) {return a[i];}
} n;
void read(_int &a) {
memset(a.a,0,sizeof a.a);
scanf("%s",s+1); ll len=strlen(s+1),js=7;
for (ll i=len; i; i--) {
if (++js==8) ++a[0],js=0;
a[a[0]]=a[a[0]]+mi[js]*(s[i]-'0');
}
}
ll divide(_int &x,ll p) {
ll ys=0,o=x[0];
x[0]=0;
for (ll i=o; i; i--) {
ys=ys*gmo+x[i];
x[i]=ys/p;
ys-=x[i]*p;
if (x[i] && !x[0]) x[0]=i;
}
return ys;
}
int main() {
freopen("password.in","r",stdin);
// freopen("password.out","w",stdout);
mi[0]=1; for (ll i=1; i<=8; i++) mi[i]=mi[i-1]*10;
read(n); cin>>p>>k;
while (n[0]) np[++np[0]]=divide(n,p);
f[o][0][1][0]=1;
for (ll i=np[0]; i; i--) {
o=1-o;
memset(f[o],0,sizeof f[o]);
for (ll jj=0; jj<=np[0]-i; jj++)
for (ll p1=0; p1<p; p1++)
for (ll p2=0; p2<p; p2++)
for (ll k=0; k<=1; k++) //卡上界
for (ll kk=0; kk<=1; kk++)
for (ll zz=0; zz<=1; zz++) //第i位有没有进位
for (ll z=0; z<=1; z++) { //第i-1位有没有进位
ll fa=p1+p2+z;
//是否要求进位
if (zz==0 && fa>=p) continue;
if (zz==1 && fa<p) continue;
// if (i==1 && z==1) continue; //第一位没得进位
// if (i==np[0] && zz==1) continue; //最后一位不能向上进位
//上界
if (fa>=p) fa-=p;
if (kk==0 && k==1) continue;
if (kk==1 && fa>np[i]) continue;
if (kk==1 && k==0 && fa==np[i]) continue;
if (kk==1 && k==1 && fa <np[i]) continue;
// printf("%d %d %d from %d %d %d,[%d,%d]\n",jj+z,k,z,jj,kk,zz,p1,p2);
add(f[o][jj+z][k][z], f[1-o][jj][kk][zz]);
}
}
ll ans=0;
for (ll i=k; i<=np[0]; i++) {
add(ans, f[o][i][1][0] + f[o][i][0][0]);
}
cout<<ans<<endl;
}

本文探讨了库默尔定理及其在数位动态规划中的应用,通过具体的数学问题,展示了如何利用该定理解决组合数学中的进位计数问题。
319

被折叠的 条评论
为什么被折叠?



