zoukankan      html  css  js  c++  java
  • kuangbin带你飞

    https://vjudge.net/contest/70324


     A - Beautiful numbers

    统计区间内的,被数位上各个为零数字整除的数的个数。

    下面是暴力的数位dp写法,绝对会TLE的,因为这个要深入到每个数字的最后才能判断是否合法。因为(错误的状态设计导致完全变成暴力dfs搜索)记忆化的意义在询问不多的时候用处不大就去掉了。果然2400分的题不能这么暴力。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[19];
    int d[10];
    ll dfs(int pos,bool limit,ll sum) {
        if(pos==-1) {
            for(int i=1; i<=9; i++) {
                if(d[i]) {
                    if(sum%i)
                        return 0;
                }
            }
            return 1;
        }
        int up=limit?a[pos]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            if(i)
                d[i]++;
            ans+=dfs(pos-1,limit && i==a[pos],sum*10ll+i);
            if(i)
                d[i]--;
        }
        return ans;
    }
    
    ll solve(ll x) {
        //特殊处理0
        if(x==0)
            return 1;
    
        int pos=0;
        while(x) {
            a[pos++]=x%10;
            x/=10;
        }
    
        memset(d,0,sizeof(d));
        return dfs(pos-1,true,0);
    }
    
    int main() {
        int t;
        scanf("%d",&t);
        ll le,ri;
        while(t--) {
            scanf("%lld%lld",&le,&ri);
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }
    View Code

    没想出来怎么解决,去查了题解,题解暗示说,这样是和最小公倍数有关的。好像的确很有道理,细节只能自己想了。

    首先考虑1~9的最小公倍数,也就是 $1*2^3*3^2*5*7=2520$ ,题解提到一个充要条件,就是一个数假如要能被某些数整除,等价于被这些数的最小公倍数整除,这个充要条件的正确性可以由质因数分解得知,就是说这个数的质因数分解必须比他的各个数位的质因数分解“高”,也就比各个数位的质因数分解的“轮廓”也就是最小公倍数“高”。(注: $lcm(a,b)=frac{a*b}{gcd(a,b)}$ ,且满足结合律)

     然后怎么计数呢?这里受到之前做的数位dp的启发,由下一位的数位dp推出上一位的状态。设计状态的时候借鉴别人的思路, $dp[i][j][k]$ 表示 $i$ 位数中能整除前面数位的最小公倍数 $j$ 的且模2520的余数为 $k$ 的数的个数。

    假设某一位的枚举值为 $a$ ,那么 $dp[i][j][k]+=dp[i-1][lcm(j,a)][(k*10+a)%p]$ ,比如现在要求的数位是2836,现在已经枚举过了2,当前在处理8,枚举千位上的值,i=2,j=2,k=2,当千位枚举3时,向下转移一个i=1,j=6,k=23,意义是显然的,因为你多了一个3,必定要能整除最小公倍数6。记忆化的时候要注意,数位受限时不能取用dp值也不能更新dp值

    最后还要注意这样做会MLE,改进的方法是给每个可能的1~9的任意组合生成的最小公倍数都生成一个id值或者(假的)hash值,方法是枚举2520的各个质因数统计最后发现是48个。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[19];
    ll dp[19][48][2520];
    
    int id[2521];
    
    void gen_id() {
        int top=0;
        for(int i=1; i<=8; i*=2) {
            for(int j=1; j<=9; j*=3) {
                for(int k=1; k<=5; k*=5) {
                    for(int l=1; l<=7; l*=7) {
                        id[i*j*k*l]=top++;
                    }
                }
            }
        }
    }
    
    ll dfs(int pos,bool limit,int lcm,int sum) {
        if(pos==-1){
            return sum%lcm==0;
        }
    
        if(!limit&&dp[pos][id[lcm]][sum]!=-1)
            return dp[pos][id[lcm]][sum];
    
        int up=limit?a[pos]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            ans+=dfs(pos-1,limit && i==a[pos],i?(i*lcm)/__gcd(i,lcm):lcm,(sum*10+i)%2520);
        }
    
        return !limit?dp[pos][id[lcm]][sum]=ans:ans;
    }
    
    ll solve(ll x) {
        //特殊处理0
        if(x==0)
            return 1;
    
        int pos=0;
        while(x) {
            a[pos++]=x%10;
            x/=10;
        }
    
        return dfs(pos-1,true,1,0);
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
        gen_id();
    
        int t;
        scanf("%d",&t);
        ll le,ri;
        while(t--) {
            scanf("%lld%lld",&le,&ri);
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }
    View Code

    最后需要注意是 return !limit?dp[pos][id[lcm]][sum]=ans:ans; ,是当不受限的时候才记录dp,这里WA的一发,不过因为是有两组数据所以很快联想到了。

    B - XHXJ's LIS


    C - 不要62

    当然有更简单的数位dp写法。根据自动机的知识我们只需要记录上一位是不是6就可以了。最后还WA了一发是因为dp的第二维开小了。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[6];
    ll dp[6][2];
    
    ll dfs(int pos,bool limit,int st) {
        if(pos==-1){
            return 1;
        }
    
        if(!limit&&dp[pos][st]!=-1)
            return dp[pos][st];
    
        int up=limit?a[pos]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            if(i==4)
                continue;
            if(st==1&&i==2)
                continue;
            ans+=dfs(pos-1,limit && i==a[pos],i==6);
        }
    
        return !limit?dp[pos][st]=ans:ans;
    }
    
    ll solve(ll x) {
        //特殊处理0
        if(x==0)
            return 1;
    
        int pos=0;
        while(x) {
            a[pos++]=x%10;
            x/=10;
        }
    
        return dfs(pos-1,true,0);
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
        ll le,ri;
        while(~scanf("%lld%lld",&le,&ri)) {
            if(le==0&&ri==0)
                break;
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }
    View Code

    E - Round Numbers

    这里是二进制的数位dp,首先先把基数改成2。然后我们思考怎么设计状态可以使得子问题容易重复,一个很显然的设计方法就是dp[i][j]表示i位数,有j个0的数的个数。那么转移的时候每一步可以选择0或1,每次选择0的时候cnt0-1,最后pos==-1的时候要判断cnt0是否恰为0。需要注意的是虽然在求解7位数是只有3个0的状态是没有用的,但是不代表他不需要被计算,因为在11位数的时候可以先选1个前导1,3个0,转移到7位数的状态,这时候7位数选3个是有用的。

    再想想前导0会有什么影响呢?因为前导0中的0是不算的,所以要分开处理一下。

    但是上面的状态设计方法是有问题的,因为i位数中有j个0的数的个数不容易区分前导0的贡献,导致dp[i][j]的值和实际要求的不一致,在从高位向低位转移时“前导0”是允许存在的,但是单独计算的时候是不可以的。一个解决的办法是再引入cnt1变成dp[i][j][k],表示包括前导0的i位数中,j个非前导0,k个1的数的个数。这样可以顺利地区分前导0带来的影响。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[32];
    ll dp[32][32][32];
    
    ll dfs(int pos,bool limit,bool lead,int cnt0,int cnt1) {
        if(pos==-1){
            return cnt0>=cnt1;
        }
    
        if(!limit&&dp[pos][cnt0][cnt1]!=-1)
            return dp[pos][cnt0][cnt1];
    
        int up=limit?a[pos]:1;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            ans+=dfs(pos-1,limit&&i==a[pos],lead&&i==0,cnt0+((!lead)&&(i==0)),(cnt1+int(i==1)));
        }
    
        return (!limit)?dp[pos][cnt0][cnt1]=ans:ans;
    }
    
    ll solve(ll x) {
        int pos=0;
        while(x) {
            a[pos++]=x%2;
            x/=2;
        }
    
        return dfs(pos-1,true,true,0,0);
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
        ll le,ri;
        while(~scanf("%lld%lld",&le,&ri)) {
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }
    View Code

    又被运算符结合坑了,加法运算符比逻辑与运算符的优先级还要高。

    上面的代码意思是,dp[i][j][k]表示包括前导0的i位数中,j个非前导0,k个1的数的个数。而lead的真假已经包含在上述的三维(其实只需要两维)中了,所以dp[i][j][k]的存储只受limit影响。


    G - B-Numbers

    数位dp的水题,想清楚状态机怎么运行就可以了。

    st0:当前”“,当遇到1时转到st1。

    st1:当前”1“,当遇到1时回到本身,当遇到3时转到st2,否则转到st0。

    st2:已发现”13“,无论遇到什么都是回到本身。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    const int MAX_LEN=10;
    int di[MAX_LEN+1];
    ll dp[MAX_LEN+1][3][13];
    
    ll dfs(int len,bool limit,bool lead,int st,int r) {
        if(len==0){
            return (st==2)&&(r%13==0);
        }
    
        if(!limit&&dp[len][st][r]!=-1)
            return dp[len][st][r];
    
        int up=limit?di[len]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            if(st==2){
                ans+=dfs(len-1,limit&&i==di[len],lead&&i==0,st,(r*10+i)%13);
            }
            else{
                int nst=0;
                if(i==1)
                    nst=1;
                else if(i==3){
                    if(st==1)
                        nst=2;
                }
                ans+=dfs(len-1,limit&&i==di[len],lead&&i==0,nst,(r*10+i)%13);
            }
        }
    
        return (!limit)?dp[len][st][r]=ans:ans;
    }
    
    ll solve(ll x) {
        if(x==0)
            return 0;
    
        int len=0;
        while(x) {
            di[++len]=x%10;
            x/=10;
        }
    
        return dfs(len,true,true,0,0);
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
        ll ri;
        while(~scanf("%lld",&ri)) {
            printf("%lld
    ",solve(ri));
        }
    }
    View Code

    献上越写越短的模板。上面这个个位index为1,也有他的好处。


    H - F(x)

    给定两个数A,B,求不超过B且权不超过A的权的数的数目。一开始错误地估计了权的上界,导致自己把记忆化给去掉了。实际上权不可能超过20000,具体是多少? $9*sumlimits_{i=1}^{8}2^iapprox9*2^9$ ,5000多一点吧?

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[10];
    int dp[10][20005];
    
    int weight;
    
    int dfs(int pos,bool limit,int rw) {
        int base=1ll<<pos;
    
        if(pos==-1){
            return 1;
        }
    
        if(!limit&&~dp[pos][rw])
            return dp[pos][rw];
    
        int up=limit?a[pos]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            if(i*base>rw)
                break;
            ans+=dfs(pos-1,limit && i==a[pos],rw-i*base);
        }
    
        return !limit?dp[pos][rw]=ans:ans;
    }
    
    int solve(int x) {
        //特殊处理0
        if(x==0)
            return 1;
    
        int pos=0;
        while(x) {
            a[pos++]=x%10;
            x/=10;
        }
    
        return dfs(pos-1,true,weight);
    }
    
    void cal_weight(int A){
        weight=0;
        int base=1;
        while(A) {
            weight+=base*(A%10);
            A/=10;
            base<<=1;
        }
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
    
        int t;
        scanf("%d",&t);
        for(int i=0;i<t;i++){
            int A,B;
            scanf("%d%d",&A,&B);
            cal_weight(A);
            printf("Case #%d: %d
    ",i+1,solve(B));
        }
    }
    View Code

     I - BCD Code

    这个一看就知道是用自动机就可以了。但是具体怎么建我就陷入了沉思。所以说瘸腿就是瘸腿啊,AC自动机还是要会。多模字符串匹配AC自动机。

  • 相关阅读:
    String 尺取法
    dos 命令
    又见01背包
    01串
    Python-Matplotlib 10 面向对象 VS Matlab Style
    [ 转 ] Fiddler 教程
    Android 查看设备文件
    Python-Matplotlib 9 颜色和样式 [r]
    Python-Matplotlib 8 箱形图
    Python-Matplotlib 7 饼状图
  • 原文地址:https://www.cnblogs.com/Yinku/p/10445989.html
Copyright © 2011-2022 走看看