数位DP入门——FZU 2113,HDU 3943,HDU 3271,HDU - 1336 ,HDU 3967,HDU 3565

数位DP解决的通常是在一个较大的范围内满足条件的数字数量(或者满足条件的最大数,或者某个数的数量),因为满足的要求通常与数位上的数字有关,所以可以对数字长度以及数位状态来动态规划,数位DP通常有两种写法,一种是预处理出DP数组,然后对询问进行处理,另一种则是在询问中dfs搜索时记忆化DP数组,我觉得第二种比较好实现,后面的题目也都是用的这种方法。数位统计比较需要考虑的就是数字边界的问题,也就是询问的数字的上限,因为dp数组记录的是数字长度为多长,满足什么条件的时候的方案数,而接触到边界时就不能直接用记忆过的dp数组的值,所以dfs函数一般会传参limit表示当前枚举所有数字是否会超上限来进行判断。

A - Jason的特殊爱好 FZU - 2113

之前写的,用的预处理dp数组的方法,dp[i][j]表示数字长度为i,首位为0到j的情况下1的数量。

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
using namespace std;
long long a, b;
unsigned long long dp[21][10];
unsigned long long ten[20];
void init(){
    ten[0] = 1;
    for (int i = 1; i <= 20; i++){ ten[i] = ten[i - 1] * 10; }
    for (int i = 1; i <= 9; i++){
        dp[1][i] = 1;
    }
    for (int i = 2; i <= 19; i++){// num of 9
        dp[i][0] = dp[i - 1][9];
        for (int j = 1; j <= 9; j++){// num head
            dp[i][j] = dp[i][j - 1]+dp[i-1][9];
            if (j == 1)dp[i][j] += ten[i-1];
        }
    }
}

unsigned long long solve(long long n){
    unsigned long long ans = 0;
    long long cnt = n, bit[25], bn = 0;
    while (cnt != 0){
        bit[++bn] = cnt % 10;
        cnt /= 10;
    }
    //bit[bn + 1] = 0;
    for (int i = bn; i >= 1; i--){
        if (bit[i] != 0)ans += dp[i][bit[i] - 1];
        if (bit[i] == 1){ ans += ((n%ten[i-1]) + 1); }
    }
    return ans;
}


int main(){
    init();
    while (cin >> a >> b){
        cout << (solve(b)-solve(a-1)) << endl;
    }
    return 0;
}

B - K-th Nya Number HDU - 3943

统计范围内有个特定个数4和特定个数7的数字的数量,还要回答范围内第q个满足满足的数是几。用二分解决的第q大询问。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
ll dp[25][25][25];//weishu,  4num,7num
int wei[30];
ll p,q,xx;
ll x,y,cc,tot;

ll dfs(int len,int four,int seven,bool limit){
    if(len==0)return four==0&&seven==0;
    if(four<0||seven<0)return 0;
    if(!limit&&dp[len][four][seven]!=-1)return dp[len][four][seven];
    int ed=limit?wei[len]:9;
    ll ans=0;
    for(int i=0;i<=ed;i++){
        if(i==4)ans+=dfs(len-1,four-1,seven,limit&&i==ed);
        else if(i==7)ans+=dfs(len-1,four,seven-1,limit&&i==ed);
        else ans+=dfs(len-1,four,seven,limit&&i==ed);
    }
    if(!limit)dp[len][four][seven]=ans;
    return ans;
}

ll cal(ll up){
    tot=0;
    while(up!=0){
        wei[++tot]=up%10;
        up/=10;
    }
    ll ans=dfs(tot,x,y,1);
    return ans;
}

int main(){
    int T;
    scanf("%d",&T);
    int cas=0;
    while(T--){
        cas++;
        printf("Case #%d:\n",cas);
        memset(dp,-1,sizeof(dp));
        scanf("%lld%lld%lld%lld",&p,&q,&x,&y);
        ll unum=cal(q);
        ll dnum=cal(p);
        ll num=unum-dnum;
        scanf("%d",&cc);
        for(int i=0;i<cc;i++){
            scanf("%lld",&xx);
            if(xx>num)printf("Nya!\n");
            else{
                    ll lef=p+1,rig=q,ans;
                    while(lef<=rig){
                        ll mid=(lef+rig)/2;
                        ll cnt=cal(mid)-dnum;
                        if(cnt==xx){ans=mid;rig=mid-1;}
                        else if(cnt>xx){rig=mid-1;}
                        else {lef=mid+1;}
                    }
                cout<<lef<<endl;
            }
        }
    }
    return 0;
}

C - SNIBB HDU - 3271

要求统计数字在k进制下的数位和为m的数量。就是在处理上限时从整除10变成整除k就行了。

//x和y的大小是不确定的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
int dp[105][305];//weishu,  4num,7num
int wei[105];
int q,x,y,b,m,k,tot;

ll dfs(int len,int sum,bool limit,int base){
    if(sum>m)return 0;
    if(len==0)return sum==m;
    if(!limit&&dp[len][sum]!=-1)return dp[len][sum];
    int ed=limit?wei[len]:base-1;
    int ans=0;
    for(int i=0;i<=ed;i++){
        ans+=dfs(len-1,sum+i,limit&&i==ed,base);
    }
    if(!limit)dp[len][sum]=ans;
    return ans;
}

ll cal(int up,int base){
    if(up<0)return 0;
    tot=0;
    while(up!=0){
        wei[++tot]=up%base;
        up/=base;
    }
    int ans=dfs(tot,0,1,base);
    return ans;
}

int main(){
    int cas=0;
    while(~scanf("%d",&q)){
        cas++;
        memset(dp,-1,sizeof(dp));
        scanf("%d%d%d%d",&x,&y,&b,&m);
        if(x>y)swap(x,y);
        if(q==2)scanf("%d",&k);
        int unum=cal(y,b);
        int dnum=cal(x-1,b);
        int num=unum-dnum;
        printf("Case %d:\n",cas);
        if(q==1)printf("%d\n",num);
        else{
            if(k>num){printf("Could not find the Number!\n");continue;}
            int lef=x,rig=y,ans;
            while(lef<=rig){
                int mid=(lef+(ll)rig)/2;
                if(cal(mid,b)-dnum>=k){ans=mid;rig=mid-1;}
                else lef=mid+1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

D - Word Index HDU - 1336

合法要求是字典序单调增,给你序列询问你这是第几个,不合法输出0

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2000005;
int dp[10][27][2];
int wei[10];

int dfs(int len,int last,int status,int limit){
    if(len==0)return status==1;
    if(!limit&&dp[len][last][status]!=-1)return dp[len][last][status];
    int ed=limit?wei[len]:26;
    int ans=0;
    if(status==0)ans+=dfs(len-1,last,0,0);
    for(int i=last+1;i<=ed;i++){
        ans+=dfs(len-1,i,1,limit&&i==ed);
    }
    if(!limit)dp[len][last][status]=ans;
    return ans;
}

ll cal(char *str){
   int len=strlen(str);
   for(int i=1;i<=len;i++){
    wei[i]=str[len-i]-'a'+1;
   }
   return dfs(len,0,0,1);
}

char str[10];
int main(){
    memset(dp,-1,sizeof(dp));
    while(~scanf("%s",str)){
        int len=strlen(str);
        bool f=1;
        for(int i=1;i<len;i++){
            if(str[i]<=str[i-1]){f=0;break;}
        }
        if(f==0)printf("0\n");
        else printf("%d\n",cal(str));
    }
    return 0;
}

E - Zero’s Number HDU - 3967

求数字分成两部分相加%k==0的方案数,dp[i][j][k][l]表示剩余长度为i的段中,分界点为j,比i高的位置的和modk剩余的数字为k,前面是否全为0的方案数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2000005;
int wei[30];
ll k,l,r;
ll dp[22][22][22][2],pw[20];
//dp[i][j][k][l]表示剩余长度为i的段中,分界点为j,比i高的位置的和modk剩余的数字为k,前面是否全为0的方案数。
ll dfs(int len,int pos,int res,int f,int limit){
    if(len==0)return res==0;//最终序列的mod不为0,不满足条件
    if(!limit&&dp[len][pos][res][f]!=-1)return dp[len][pos][res][f];
    if(len<=pos&&f==0)return 0;//前面那段都是0了,不满足条件
    ll ans=0;
    int ed=limit?wei[len]:9;
    for(int i=0;i<=ed;i++){
        int nxt,nf=f;
        if(len>pos)nxt=(ll(res)+i*pw[len-pos-1])%k;
        else nxt=(ll(res)+i*pw[len-1])%k;
        if(nf==0&&i>0)nf=1;//分段点前存在不为0的点
        ans+=dfs(len-1,pos,nxt,nf,limit&&i==ed);
    }
    if(!limit)dp[len][pos][res][f]=ans;
    return ans;
}

ll solve(ll v){
    int tot=0;
    while(v!=0){//求出每一位的数字
        wei[++tot]=v%10;
        v/=10;
    }
    ll ans=0;
    for(int i=1;i<tot;i++){//枚举分段点
        ans+=dfs(tot,i,0,0,1);
    }
    return ans;
}

int main(){
    pw[0]=1;
    for(int i=1;i<=18;i++)pw[i]=pw[i-1]*10;
    while(~scanf("%lld%lld%lld",&l,&r,&k)){
        memset(dp,-1,sizeof(dp));
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}

F - Bi-peak Number HDU - 3565

两个山坡形的数字组合起来就满足要求,要求找出给定范围内数位和最大的那个数,参考的别人的解法,dp[i][j]表示当前长度为i,状态是j的情况下后续数字使满足条件的最大数位和

include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2000005;
ull a,b;
int dp[30][10][7];//bitnum lastn status
int up[30],down[30];
int tot;
//0前导 1上不能下 2能上能下 3能下或二 45能上能下 6能下或结束
int dfs(int len,int last,int status,int ulimit,int dlimit){
    if(len==0)return status==6?0:-1 ;
    if(!ulimit&&!dlimit&&dp[len][last][status]!=-1)return dp[len][last][status];
    int upp=ulimit?up[len]:9;
    int dpp=dlimit?down[len]:0;
    int ans=-1;
    int nxt;
    for(int i=dpp;i<=upp;i++){
            if(status==0){if(i!=0)nxt=1;else nxt=0;}
            if(status==1){if(i>last)nxt=2;else nxt=-1;}
            if(status==2){if(i>last)nxt=2;else if(i<last)nxt=3;else nxt=-1;}
            if(status==3){if(i>last)nxt=4;else if(i==last){if(i)nxt=4;else nxt=-1;}else nxt=3;}
            if(status==4){if(i>last)nxt=5;else nxt=-1;}
            if(status==5){if(i>last)nxt=5;else if(i<last)nxt=6;else nxt=-1;}
            if(status==6){if(i<last)nxt=6;else nxt=-1;}
            if(nxt!=-1){
                int cc=dfs(len-1,i,nxt,ulimit&&i==upp,dlimit&&i==dpp);
                if(cc!=-1)ans=max(ans,cc+i);
            }
    }
    if(!ulimit&&!dlimit)dp[len][last][status]=ans;
    return ans;
}

int cal(ull a,ull b){
    tot=0;
    while(a!=0||b!=0){
        up[++tot]=b%10;
        b/=10;
        down[tot]=a%10;
        a/=10;
    }
    return dfs(tot,0,0,1,1);
}

int main(){
    int T;
    scanf("%d",&T);
    int cas=0;
    memset(dp,-1,sizeof (dp));
    while(T--){
            cas++;
            cin>>a>>b;
            int ans=cal(a,b);
            if(ans==-1)ans=0;
            printf("Case %d: %d\n",cas,ans);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值