数位dp

数位dp

T3:

应该先从这道题入手数位dp,首先理解数位dp,dp了什么东西

它是把一个数从低位拆到高位,相当于是较低位的方案数是较高位的子问题

个人认为数位dp写记忆化搜索更容易理解一点

看这道题,首先我们让我们分别求 0 9l r 出现的次数,我们先把 0 9 分别求,再把 l r 差分一下,就是用 1 r 减去 1 l1

编码时,我们用now来存储统计0~9中的一个数字,下面以now=2为例

我们存一下 dp[pos][sum] 表示最后pos位,pos位前面前面有sum个数=now

举个例子:

然后我们下传时要用lead表示是否有前导0,limit表示当前最高位是否有数位限制

个人理解需要下传是否有前导0的原因是因为我们要在统计0出现的个数,而不能把前导0也给统计上,此处仅为本人个人想法,如有纠正,请发表评论

ps:已经证明了,我将所有lead删去之后,其余数位全部正确,只有统计0的个数时出现错误了,说明下传前导0,只与统计0时不能统计上前导0有关

记搜上代码(更容易理解一些):

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int l,r,now;
int num[N],dp[N][N];
int dfs(int pos,int sum,bool lead,bool limit){
    int ans=0;
    if(pos==0)  return sum;//说明已经精确到一个数了,直接返回这个数中的now个数
    if(!lead&&!limit&&dp[pos][sum]!=-1)  return dp[pos][sum];//因为我们记忆化的是没有前导0,没有限制的大众情况
    int up=9;
    if(limit)  up=num[pos];//up表示这一位最高到哪里,没有限制就是9
    for(int i=0;i<=up;i++){
        if(i==0&&lead)  ans+=dfs(pos-1,sum,1,limit&&i==up);//下传前导0情况
        else if(i==now)  ans+=dfs(pos-1,sum+1,0,limit&&i==up);//这一位出现了now
        else  ans+=dfs(pos-1,sum,0,limit&&i==up);
    }
    if(!lead&&!limit)  dp[pos][sum]=ans;//记忆化搜索
    return ans;
}
int solve(int x){
    int len=0;
    while(x){
        num[++len]=x%10;
        x/=10;
    }
    memset(dp,-1,sizeof(dp));
    return dfs(len,0,1,1);
}
signed main(){
    scanf("%lld%lld",&l,&r);
    for(int i=0;i<=9;i++){
        now=i;
        printf("%lld ",solve(r)-solve(l-1));
    }
}

T1:

就这题还放T1,它什么难度,你心里没点B数吗

B数要满足两个限制,一个是要出现13,另一个是要被13整除

也就是说我们只有满足这个条件才能被统计到,就是转化一下,T3是出现now可以被统计,我们这个只不过是更复杂了一些,所以判断时也更复杂,但是没有本质区别

对于除以13的余数这个玩意,考虑从上一位下传到下一位,是把上一位除以13得到的余数*10再加上这一位的数在模上13,就是这一位剩下的余数,所以我们下传一个这一位的余数y

对于是否包含13这个限制我们设三种状态,0没有任何包含,1上一位是1,2已经包含13,然后我们根据这个状态转移就好了

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int n;
int num[N],dp[N][N][N];
int check(int x,int c){
    if(c==2)  return 2;//注(卡了我好久):只要有13,就可以算,所以放在最前面
    if(x==1)  return 1;
    if(c==1&&x==3)  return 2;
    return 0;
}
int dfs(int pos,int y,int c,int limit){
    int ans=0;
    if(!pos)  return !y&&c==2;//判断这个数是否位B数
    if(!limit&&dp[pos][y][c]!=-1)  return dp[pos][y][c];///记搜
    int up=9;
    if(limit)  up=num[pos];
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,(y*10+i)%13,check(i,c),limit&&i==up);
    }
    if(!limit)  dp[pos][y][c]=ans;
    return ans;
}
signed main(){
    while(scanf("%lld",&n)!=EOF){
        int len=0;
        while(n){
            num[++len]=n%10;
            n/=10;
        }
        memset(dp,-1,sizeof(dp));
        printf("%lld\n",dfs(len,0,0,1));
    }
    return 0;
}

T2:

切了,和T1思路基本一样,先把n转化为二进制,只需要下传二进制数位0/1的个数即可,注意也要处理前导0情况不能算

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=50;
int l,r;
int num[N],dp[N][N][N];
int dfs(int pos,int sum0,int sum1,int lead,int lim){
    if(!pos)  return sum0>=sum1;
    if(!lead&&!lim&&dp[pos][sum0][sum1]!=-1)  return dp[pos][sum0][sum1];
    int up=1,ans=0;
    if(lim)  up=num[pos];
    for(int i=0;i<=up;i++){
        if(i==0&&lead)  ans+=dfs(pos-1,sum0,sum1,1,lim&&i==up);
        else if(i==0)  ans+=dfs(pos-1,sum0+1,sum1,0,lim&&i==up);
        else  ans+=dfs(pos-1,sum0,sum1+1,0,lim&&i==up);
    }  
    if(!lead&&!lim)  dp[pos][sum0][sum1]=ans;
    return ans;
}
int solve(int x){
    int len=0;
    while(x){
        num[++len]=x%2;
        x/=2;
    }
    memset(dp,-1,sizeof(dp));
    return dfs(len,0,0,1,1);
}
signed main(){
    scanf("%lld%lld",&l,&r);
    printf("%lld",solve(r)-solve(l-1));
}

ps:此题如果wa了的话,会发现显示数据是错误的,但是wa了跟数据无关,如果是90分可以看一下是不是空间开小了

T5:

正确的做法lz懒得写,直接二分切了

我们显然容易判断 1 x 中幸运数字的个数,然后就拿这个二分一下就可以做了

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60,M=1e10;
int T,n;
int dp[N][4],num[N];
int pan(int x,int c){
    if(c==3)  return 3;
    if(x==6)  return c+1;
    return 0;
}
int dfs(int pos,int c,bool lim){
    if(!pos)  return c==3;
    if(!lim&&dp[pos][c]!=-1)  return dp[pos][c];
    int up=9,ans=0;
    if(lim)  up=num[pos];
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,pan(i,c),lim&&i==up);
    }
    if(!lim)  dp[pos][c]=ans;
    return ans;
}
int check(int x){
    int len=0;
    while(x){
        num[++len]=x%10;
        x/=10;
    }
    memset(dp,-1,sizeof(dp));
    return dfs(len,0,1);
}
signed main(){
    scanf("%lld",&T);
    while(T--){
        scanf("%lld",&n);
        int l=666,r=M;
        while(l<r){
            int mid=(l+r)/2;
            if(check(mid)>=n)  r=mid;
            else  l=mid+1;
        }
        printf("%lld\n",l);
    }
}

T6:

直接切了

最板子的数位dp,然后把ans+=,改为ans*=就过了,最后再判断掉sum(0)的情况就行了

写完后喵了一眼题解,不懂啊为什么要用快速幂,既复杂又慢,像个小丑🤡

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60,mod=1e7+7;
int n;
int dp[N][N],num[N];
int dfs(int pos,int sum,bool lim){
    if(!pos)  return sum;
    if(!lim&&dp[pos][sum]!=-1)  return dp[pos][sum];
    int up=1,ans=1;
    if(lim)  up=num[pos];
    for(int i=0;i<=up;i++){
        if(sum==0&&pos==1&&i==0)  continue;//特判0的情况
        ans*=dfs(pos-1,sum+(i==1),lim&&i==up);//加改为乘
        ans%=mod;
    }  
    if(!lim)  dp[pos][sum]=ans;
    return ans;
}
signed main(){
    scanf("%lld",&n);
    int len=0;
    while(n){
        num[++len]=n%2;
        n/=2;
    }
    memset(dp,-1,sizeof(dp));
    printf("%lld",dfs(len,0,1));
}
posted @   daydreamer_zcxnb  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示