数位dp合集

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/monochrome00/article/details/82355754

HDU - 3652 - B-number

题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=3652>


题意:

输出1~n中包含“13”且能被13整除的个数。(1 <= n <= 1000000000)


题解:

数位dp的关键就是状态的选取。

这道题的“13”是否被包含很好处理。要保证被13整除只需再添加一维记录余数即可。

#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
typedef long long ll;
int dp[20][2][2][20],dig[20];
int dfs(int len,bool yi,bool san,int mod,bool e){
    if(!len) return san&&!mod;
    if(!e&&~dp[len][yi][san][mod]) return dp[len][yi][san][mod];
    int u=e?dig[len]:9,ret=0;
    for(int i=0;i<=u;i++){
        ret=(ret+dfs(len-1,i==1,san||(i==3&&yi),((mod*10)+i)%13,e&&i==u));
    }
    if(!e) dp[len][yi][san][mod]=ret;
    return ret;
}
int solve(int x){
    int cnt=0;
    while(x){
        dig[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,0,0,0,1);
}
int main()
{
    int t,n;
    memset(dp,-1,sizeof(dp));
    while(scanf("%d",&n)!=EOF){
        printf("%d\n",(solve(n)-solve(0)));
    }
}

 


SPOJ - BALNUM - Balanced Numbers

题目链接<https://cn.vjudge.net/problem/SPOJ-BALNUM>


题意:

给出一段范围[A, B],问你在这个范围中平衡数的个数。

平衡数定义为每一位的数字,如果是奇数就出现偶数次,如果是偶数就出现奇数次,当然也可以不出现。


题解:

开始打算开个11维的数组,想想就很夸张。

其实这题可以用三进制的数来存储状态。表示没有出现,奇数次,偶数次。

3^{10}=59 049。所以第二维开个60000大小就够了。

还有要注意前导零。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[20][60000],dig[20],a[20];
ll dfs(ll len,ll stt,bool e,bool fst){
    if(!len){
        if(!fst) return 0;
        for(ll i=0;i<=9;i++){
            if(i%2==1) if(stt/a[i]%3==1) return 0;
            if(i%2==0) if(stt/a[i]%3==2) return 0;
        }
        return 1;
    }
    if(!e&&fst&&~dp[len][stt]) return dp[len][stt];
    ll u=e?dig[len]:9,ret=0;
    for(ll i=0;i<=u;i++){
        ll tmp=stt;
        if(i==0&&!fst);
        else{
            if(tmp/a[i]%3<2) tmp+=a[i];
            else tmp-=a[i];
        }
        ret+=dfs(len-1,tmp,e&i==u,fst||(i!=0));
    }
    if(!e&&fst) dp[len][stt]=ret;
    return ret;
}
ll solve(ll x){
    ll cnt=0;
    while(x){
        dig[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,0,1,0);
}
int main(){
    int t;
    ll n,m;
    scanf("%d",&t);
    a[0]=1;
    for(ll i=1;i<=10;i++)
        a[i]=a[i-1]*3;
    memset(dp,-1,sizeof(dp));
    while(t--){
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",solve(m)-solve(n-1));
    }
}

HDU - 4352 - XHXJ's LIS

题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=4352>


题意:

求出[L,R]中最长上升子序列为k的个数。


题解:

通过nlogn求上升子序列的方法记录状态:

根据状压的思想,用一个10位的数字记录0~9是否在序列内,每次新加入一个数就把大于它的最小值替换掉。

把k也放在状态里面,不然会超时。


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dg[20],dp[20][1<<12][12];
ll dfs(ll len,ll stt,ll maxn,bool e,bool fst,ll k){
    if(!len) return maxn==k;
    if(!e&&fst&&~dp[len][stt][k]) return dp[len][stt][k];
    ll u=e?dg[len]:9,ret=0;
    for(ll i=0;i<=u;i++){
        if(i==0&&!fst){
            ret+=dfs(len-1,0,0,e&&i==u,0,k);
            continue;
        }
        if(stt&(1<<i)){
            ret+=dfs(len-1,stt,maxn,e&&i==u,fst||i>0,k);
            continue;
        }
        ll tmp=stt; bool flag=false;
        for(ll j=i+1;j<=9;j++){
            if(stt&(1<<j)){
                tmp-=1<<j;
                tmp+=1<<i;
                flag=true;
                break;
            }
        }
        if(flag){
            ret+=dfs(len-1,tmp,maxn,e&&i==u,fst||i>0,k);
            continue;
        }
        else{
            tmp+=1<<i;
            ret+=dfs(len-1,tmp,maxn+1,e&&i==u,fst||i>0,k);
        }
    }
    if(!e&&fst) dp[len][stt][k]=ret;
    return ret;
}
ll solve(ll x,ll k){
    ll cnt=0;
    while(x){
        dg[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,0,0,1,0,k);
}
int main(){
    ll t,n,m,k,cs=0;
    scanf("%lld",&t);
    memset(dp,-1,sizeof(dp));
    while(t--){

        scanf("%lld%lld%lld",&n,&m,&k);
        printf("Case #%lld: ",++cs);
        printf("%lld\n",solve(m,k)-solve(n-1,k));
    }
}

猜你喜欢

转载自blog.csdn.net/monochrome00/article/details/82355754