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

39 篇文章 0 订阅
31 篇文章 0 订阅

题面

这里写图片描述

这里写图片描述

分析

首先 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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值