kuangbin带你飞 - 专题十五 - 数位dp

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\n",solve(ri)-solve(le-1));
    }
}

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

首先考虑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\n",solve(ri)-solve(le-1));
    }
}

最后需要注意是 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\n",solve(ri)-solve(le-1));
    }
}


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\n",solve(ri)-solve(le-1));
    }
}

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

上面的代码意思是,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\n",solve(ri));
    }
}

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


H - F(x)

给定两个数A,B,求不超过B且权不超过A的权的数的数目。一开始错误地估计了权的上界,导致自己把记忆化给去掉了。实际上权不可能超过20000,具体是多少? $9*\sum\limits_{i=1}^{8}2^i\approx9*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\n",i+1,solve(B));
    }
}


I - BCD Code

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

原文地址:https://www.cnblogs.com/Yinku/p/10445989.html

时间: 2024-09-30 16:29:05

kuangbin带你飞 - 专题十五 - 数位dp的相关文章

[kuangbin带你飞]专题十六 KMP &amp; 扩展KMP &amp; Manacher :G - Power Strings POJ - 2406(kmp简单循环节)

[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher G - Power Strings POJ - 2406 题目: Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc" and b = "def" then a*b = "abcdef". If we think of

「kuangbin带你飞」专题十五 数位DP

传送门 A.CodeForces - 55D Beautiful numbers 题意 一个正整数是 漂亮数 ,当且仅当它能够被自身的各非零数字整除.我们不必与之争辩,只需计算给定范围中有多少个漂亮数. 思路 因为问你的是一段区间内有多少数能整除他的所有非零数位 1-9,1,一定能被任何正整数整除,1-9的最小公倍数为2520 而1-2520中真正是1-9中的最小公倍数的只有48个 dp i j k:因为dp25,2520,[2520]开不下,所以我们要进行适当离散化 index[]数组标记1-

[kuangbin带你飞]专题十 匹配问题 一般图匹配

过去做的都是二分图匹配 即 同一个集合里的点 互相不联通 但是如果延伸到一般图上去 求一个一般图的最大匹配 就要用带花树来解决 带花树模板 用来处理一个无向图上的最大匹配 看了一会还是不懂  抄了一遍kuangbin的模板熟悉了一下 还有一个一般图最大权匹配 保存下来了VFK菊苣的模板题代码当作板子 http://uoj.ac/submission/16359 但愿以后的比赛永远也遇不到 .. 遇到了也能抄对 .. 抄错了也能过 .. R ural1099 kuangbin模板 #include

[kuangbin带你飞]专题十 匹配问题 二分图多重匹配

二分图的多重匹配问题不同于普通的最大匹配中的"每个点只能有最多一条边" 而是"每个点连接的边数不超过自己的限定数量" 最大匹配所解决的问题一般是"每个人都有一群想加入的团体 并且一个团体只能收一个人 问有多少人可以加入一个自己喜欢的团体" 而多重匹配是 "每个人都有一群想加入的团体 每个团体可以收给定的人数 问有多少人可以加入一个自己喜欢的团体" 解决这个问题 目前看貌似有三个办法 1 拆点 一个团体可以招x个人 就把它拆成x

【算法系列学习】DP和滚动数组 [kuangbin带你飞]专题十二 基础DP1 A - Max Sum Plus Plus

A - Max Sum Plus Plus 1 https://vjudge.net/contest/68966#problem/A 2 3 http://www.cnblogs.com/kuangbin/archive/2011/08/04/2127085.html 4 5 /* 6 状态dp[i][j]有前j个数,组成i组的和的最大值.决策: 7 第j个数,是在第包含在第i组里面,还是自己独立成组. 8 方程 dp[i][j]=Max(dp[i][j-1]+a[j] , max( dp[i-

【算法系列学习】状压dp [kuangbin带你飞]专题十二 基础DP1 D - Doing Homework

https://vjudge.net/contest/68966#problem/D http://blog.csdn.net/u010489389/article/details/19218795 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<algorithm> 6 #include<cmath>

【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 G - 免费馅饼

https://vjudge.net/contest/68966#problem/G 正解一: http://www.clanfei.com/2012/04/646.html 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<algorithm> 6 #include<cmath> 7 #define IN

【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 E - Super Jumping! Jumping! Jumping!

https://vjudge.net/contest/68966#problem/E http://blog.csdn.net/to_be_better/article/details/50563344 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<algorithm> 6 #include<cmath>

【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 C - Monkey and Banana

https://vjudge.net/contest/68966#problem/C [参考]http://blog.csdn.net/qinmusiyan/article/details/7986263 [题意]:多组测试数据        每组测试数据给出n个砖块的长.宽.高,每种砖块有无穷多个,可以有三种不同的放置方法(xy;xz;yz),下面的砖要比上面的砖的长和宽都大,问最大的高度是多少. [思路]:[贪心+dp]每块砖有三种放置方法,把所有砖的所有状态都压入vector,先贪心,按面