jzoj5330 【NOIP2017提高A组模拟8.22】密码

来源:互联网 发布:淘宝 怎么红包 套现 编辑:程序博客网 时间:2024/06/10 03:48

题面

这里写图片描述

这里写图片描述

分析

首先n!中p的指数是i=0npi(有一个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(npp2w),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;}
阅读全文
0 0