数位dp

1 篇文章 0 订阅

hdu2089 不要62

题意:

求[l,r]内满足下列条件的数的个数:
1.不含4
2.不含连续62(连续的例如623,不连续的例如602)

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=10;//题目数据范围最大1e6,即最多7位
int digit[maxm];//数位分解
int d[maxm][2];//d[i][0]表示长度为i,首位不为6方案数;d[i][1]表示长度为i,首位为6方案数
int dfs(int len,bool pre6,bool limit){//pre6记录上一位是否是6,limit最高位标记
    if(!len)return 1;
    if(!limit&&d[len][pre6]!=-1){
        return d[len][pre6];
    }
    int ans=0;
    int ma=(limit?digit[len]:9);//确定枚举上界
    for(int i=0;i<=ma;i++){
        if(i==4)continue;//4不行
        if(i==2&&pre6)continue;//62连续也不行
        ans+=dfs(len-1,i==6,limit&&i==ma);
    }
    if(!limit){//只记录没有首位限制的
        d[len][pre6]=ans;
    }
    return ans;
}
int solve(int x){
    int len=0;
    memset(digit,0,sizeof digit);
    while(x){//数位分解
        digit[++len]=x%10;
        x/=10;
    }
    return dfs(len,false,true);
}
signed main(){
    memset(d,-1,sizeof d);//外面初始化一次就行了
    int l,r;
    while(scanf("%d%d",&l,&r)!=EOF){
        if(!(l+r))break;
        int ans=solve(r)-solve(l-1);
        printf("%d\n",ans);
    }
    return 0;
}

hdu4734 F(x)

题意:

定义F(x)=An2n-1 + An-12n-2 + … + A221 + A120.
给a,b求0-b中满足f(x)<=f(a)的x的个数

思路:

f(a)是根据输入变化的
容易想到记录长度为len,f(x)为sum,f(a)为fa时候的状态d(len,sum,fa),则需要三维。
经测试f(a)最大在4599,三维显然开不起,

因为题目求得是小于等于f(a)得,把题目转化成减法,从f(a)开始减,
则数组可以改成长度为len,当前剩余remain得方案数d(len,remain),二维就没问题了。

code:
#include<bits/stdc++.h>
using namespace std;
const int N=15;
const int M=5e3+5;
int digit[N];
int d[N][M];
int f(int x){
    int ans=0;
    int p=1;
    while(x){
        ans+=x%10*p;
        x/=10;
        p<<=1;
    }
    return ans;
}
int dfs(int len,int remain,int limit){
    if(!len)return remain>=0;
    if(remain<0)return 0;
    if(!limit&&d[len][remain]!=-1){
        return d[len][remain];
    }
    int ans=0;
    int ma=(limit?digit[len]:9);
    for(int i=0;i<=ma;i++){
        ans+=dfs(len-1,remain-i*(1<<(len-1)),limit&&i==ma);
    }
    if(!limit){
        d[len][remain]=ans;
    }
    return ans;
}
int solve(int a,int x){
    int len=0;
    while(x){
        digit[++len]=x%10;
        x/=10;
    }
    return dfs(len,f(a),1);
}
signed main(){
    memset(d,-1,sizeof d);
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--){
        int a,b;
        scanf("%d%d",&a,&b);
        int ans=solve(a,b);
        printf("Case #%d: %d\n",cas++,ans);
    }
    return 0;
}

poj3252 Round Numbers

题意:

求[l,r]中二进制0的个数大于等于1的个数的数

思路:

d(len,dif)表示长度为len的二进制数,其中0的个数减1的个数差值为dif的方案数
中间过程dif可能小于0无法作为数组下标,因为int最小不到-32,加上32再存就行了。

code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxm=65;
int digit[maxm];
int d[maxm][maxm];
int dfs(int len,int dif,bool lead,bool limit){
    if(!len)return dif>=0;
    if(!lead&&!limit&&d[len][dif+32]!=-1){//加上32保证不为负数
        return d[len][dif+32];
    }
    int ma=(limit?digit[len]:1);
    int ans=0;
    for(int i=0;i<=ma;i++){
        if(lead&&i==0){
            ans+=dfs(len-1,dif,true,limit&&i==ma);//前导零不计,所以dif不变
        }else{
            ans+=dfs(len-1,dif+(i==0?1:-1),false,limit&&i==ma);
        }
    }
    if(!lead&&!limit){
        d[len][dif+32]=ans;
    }
    return ans;
}
int solve(int x){
    int len=0;
    while(x){
        digit[++len]=(x&1);
        x>>=1;
    }
    return dfs(len,0,true,true);
}
signed main(){
    memset(d,-1,sizeof d);
    int l,r;
    while(scanf("%d%d",&l,&r)!=EOF){
        int ans=solve(r)-solve(l-1);
        printf("%d\n",ans);
    }
    return 0;
}

P2657 [SCOI2009]windy数

题意:

不含前导零且相邻两个数字之差至少为2的正整数被称为windy数
问[l,r]之间有多少windy数

思路:

d(len,pre)表示长度为len,上一位为pre的方案数

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=15;
int digit[maxm];
int d[maxm][maxm];
int dfs(int len,int pre,bool lead,bool limit){
    if(!len)return 1;
    if(!lead&&!limit&&d[len][pre]!=-1){
        return d[len][pre];
    }
    int ans=0;
    int ma=(limit?digit[len]:9);
    for(int i=0;i<=ma;i++){
        if(lead){//前导零的情况不用考虑差值
            ans+=dfs(len-1,i,lead&&i==0,limit&&i==ma);
        }else{//无前导的情况需要考虑差值
            if(abs(pre-i)<2)continue;//差值小于2跳过
            ans+=dfs(len-1,i,false,limit&&i==ma);
        }
    }
    if(!lead&&!limit){
        d[len][pre]=ans;
    }
    return ans;
}
int solve(int x){
    int len=0;
    while(x){
        digit[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,true,true);
}
signed main(){
    memset(d,-1,sizeof d);
    int l,r;
    scanf("%d%d",&l,&r);
    int ans=solve(r)-solve(l-1);
    printf("%d\n",ans);
    return 0;
}

hdu3555 Bomb

题意:

给n,求1-n内有多少个数含有连续的49

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=20;
int digit[maxm];
int d[maxm][maxm];
int ten[maxm];
void init(){
    memset(d,-1,sizeof d);
    ten[0]=1;
    for(int i=1;i<=18;i++){//预处理10的各个幂,dfs中要用到
        ten[i]=ten[i-1]*10;
    }
}
int dfs(int n,int len,bool pre,bool limit){//pre记录上一位是否为4
    if(!len)return 0;//这里是0
    if(!limit&&d[len][pre]!=-1){
        return d[len][pre];
    }
    int ans=0;
    int ma=(limit?digit[len]:9);
    for(int i=0;i<=ma;i++){
        if(pre&&i==9){//如果有连续49,直接记录答案
            ans+=(limit?n%ten[len-1]+1:ten[len-1]);
        }else{
            ans+=dfs(n,len-1,i==4,limit&&i==ma);
        }
    }
    if(!limit){
        d[len][pre]=ans;
    }
    return ans;
}
int solve(int x){
    int n=x;
    int len=0;
    while(x){
        digit[++len]=x%10;
        x/=10;
    }
    return dfs(n,len,false,true);
}
signed main(){
    init();
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        int ans=solve(n);
        cout<<ans<<endl;
    }
    return 0;
}

codeforces204 A. Little Elephant and Interval

题意:

求[l,r]内有多少个数首尾相同
l,r<=1e18

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=25;
int digit[maxm];
int d[maxm][maxm];//d[i][j]表示剩余长度为i,当前首位为j的方案数
int dfs(int len,int st,int ed,int limit,int pre){
    //长度,首位,尾位,最高位限制,前导零
    if(!len)return st==ed;
    if(!limit&&d[len][st]!=-1){
        return d[len][st];
    }
    int ans=0;
    int ma=(limit?digit[len]:9);
    for(int i=0;i<=ma;i++){
        int a=st;
        int b=ed;
        if(pre){//如果有前导零则i为首位
            a=i;//st=i;
        }
        if(len==1){//如果是最后一位
            b=i;//ed=i;
        }
        ans+=dfs(len-1,a,b,limit&&i==ma,pre&&i==0);
    }
    if(!limit){
        d[len][st]=ans;
    }
    return ans;
}
int solve(int n){
    int len=0;
    while(n){
        digit[++len]=n%10;
        n/=10;
    }
    return dfs(len,0,0,1,1);
}
signed main(){
    memset(d,-1,sizeof d);
    int l,r;
    while(cin>>l>>r){
        cout<<solve(r)-solve(l-1)<<endl;
    }
    return 0;
}

codeforces1036 C. Classy Numbers

题意:

给l,r,求区间[l,r]内有多少个数,满足数位不为0的个数小于等于3

思路:

d(len,now)表示长度为len,还能选择now个不为0的数的方案数
dfs过程中如果now<0,则直接返回0

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=25;
int digit[maxm];
int d[maxm][4];
int dfs(int len,int limit,int now){
    if(now<0)return 0;//超过3个不符合条件
    if(!len)return 1;
    if(!limit&&d[len][now]!=-1){
        return d[len][now];
    }
    int ma=(limit?digit[len]:9);
    int ans=0;
    for(int i=0;i<=ma;i++){
        ans+=dfs(len-1,limit&&(i==ma),now-(i!=0));
    }
    if(!limit){
        d[len][now]=ans;
    }
    return ans;
}
int solve(int n){
    int len=0;
    while(n){
        digit[++len]=n%10;
        n/=10;
    }
    return dfs(len,1,3);
}
signed main(){
    memset(d,-1,sizeof d);
    int T;
    cin>>T;
    while(T--){
        int l,r;
        cin>>l>>r;
        cout<<solve(r)-solve(l-1)<<endl;
    }
    return 0;
}

牛客 好朋友

题面:

在这里插入图片描述

思路:

dfs的过程中添加一个状态变量sta
一共四种状态:
0.啥都没有
1.0
2.00
3.007

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=25;
int digit[maxm];
int d[maxm][4];//d[i][j]表示剩余长度为i,当前状态为sta的方案数
int dfs(int len,int sta,int limit,int pre){
    //长度,状态,数位限制,前导0
    if(!len)return sta==3;//007状态
    if(!limit&&!pre&&d[len][sta]!=-1)return d[len][sta];
    int ma=limit?digit[len]:9;
    int ans=0;
    for(int i=0;i<=ma;i++){//枚举当前位
        if(sta==0){//啥都没有
            ans+=dfs(len-1,!pre&&i==0,limit&&i==ma,pre&&i==0);
        }else if(sta==1){//0
            ans+=dfs(len-1,sta+(i==0),limit&&i==ma,pre&&i==0);
        }else if(sta==2){//00
            ans+=dfs(len-1,sta+(i==7),limit&&i==ma,pre&&i==0);
        }else if(sta==3){//007
            ans+=dfs(len-1,sta,limit&&i==ma,pre&&i==0);
        }
    }
    if(!limit&&!pre){
        d[len][sta]=ans;
    }
    return ans;
}
int solve(int n){
    if(n<=0)return 0;
    int len=0;
    while(n){
        digit[++len]=n%10;
        n/=10;
    }
    return dfs(len,0,1,1);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    memset(d,-1,sizeof d);
    int T;
    cin>>T;
    int ans=0;
    while(T--){
        int l,r;
        cin>>l>>r;
        ans^=solve(r)-solve(l-1);
    }
    cout<<ans<<endl;
    return 0;
}

牛客 明七暗七

题面:

在这里插入图片描述

思路:

很容易想到二分.。
如果按照题目要求进行数位dp统计满足条件的数,有点困难。
所以反过来,统计不满足题目条件的数,n-solve(n)就是满足条件的数了。
d(i,j)表示剩余长度为i,当前模7等于j的方案数。

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=25;
int digit[maxm];
int d[maxm][7];//d[i][j]表示剩余长度为i,当前模7等于j的方案数
int dfs(int len,int sta,int limit){
    if(!len)return sta!=0;
    if(!limit&&d[len][sta]!=-1){
        return d[len][sta];
    }
    int ma=limit?digit[len]:9;
    int ans=0;
    for(int i=0;i<=ma;i++){
        if(i==7)continue;
        ans+=dfs(len-1,(sta*10+i)%7,limit&&i==ma);
    }
    if(!limit){
        d[len][sta]=ans;
    }
    return ans;
}
int solve(int n){
    int len=0;
    while(n){
        digit[++len]=n%10;
        n/=10;
    }
    return dfs(len,0,1);
}
signed main(){
    //统计1-n中满足条件的不好计算
    //正难则反,统计1-n中不满足条件的
    memset(d,-1,sizeof d);
    int m,n;
    cin>>m>>n;//m之后的第n个
    int l=m,r=1e18;
    int ans=-1;
    int temp=(m)-solve(m);//1-m中满足的
    while(l<=r){
        int mid=(l+r)/2;
        int cnt=mid-solve(mid)-temp;//1-mid中满足的减去1-(m-1)中满足的
        if(cnt>=n){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}

牛客 牛牛的随机数

题面:

在这里插入图片描述

思路:

在这里插入图片描述

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=70;
const int mod=1e9+7;
int digit[maxm];
int d[maxm][maxm][2];
int ppow(int a,int b,int mod){
    a%=mod;
    int ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
int dfs(int len,int limit,int k,int sta){
    //长度,高位限制,第k位,当前第k位的状态
    if(!len)return sta;
    if(!limit&&d[len][k][sta]!=-1)return d[len][k][sta];
    int ma=limit?digit[len]:1;
    int ans=0;
    for(int i=0;i<=ma;i++){
        ans+=dfs(len-1,limit&&i==ma,k,sta||(len==k&&i));
    }
    if(!limit){
        d[len][k][sta]=ans;
    }
    return ans;
}
int solve(int n,int k){
    int len=0;
    while(n){
        digit[++len]=n%2;
        n/=2;
    }
    return dfs(len,1,k,0);
}
signed main(){
    memset(d,-1,sizeof d);
    int T;
    cin>>T;
    while(T--){
        int l,r,ll,rr;
        cin>>l>>r>>ll>>rr;
        int ans=0;
        int p=1;
        for(int i=1;i<=60;i++){
            int cnta=solve(r,i)-solve(l-1,i);
            int cntb=solve(rr,i)-solve(ll-1,i);
            ans+=(cnta%mod)*((rr-ll+1-cntb)%mod)%mod*p%mod;
            ans+=(r-l+1-cnta)%mod*(cntb%mod)%mod*p%mod;
            ans%=mod;
            p=p*2%mod;
        }
        ans=ans*(ppow(r-l+1,mod-2,mod)*ppow(rr-ll+1,mod-2,mod)%mod)%mod;
        cout<<ans<<endl;
    }
    return 0;
}

AtCoder Beginner Contest 161 |D - Lunlun Number

题意:

lunlun数定义为任意相邻数字差值小于等于1的数,问第k个lunlun数是多少

思路:

二分+数位dp

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int digit[25];
int d[25][15][2];
int dfs(int len,int limit,int pre,int last,int ok){
    if(!len)return ok;
    if(!limit&&!pre&&d[len][last][ok]!=-1)return d[len][last][ok];
    int ans=0;
    int ma=(limit?digit[len]:9);
    for(int i=0;i<=ma;i++){
        ans+=dfs(len-1,limit&&i==ma,pre&&i==0,i,ok&&(pre||abs(i-last)<=1));
    }
    if(!limit&&!pre){
        d[len][last][ok]=ans;
    }
    return ans;
}
int solve(int x){
    int len=0;
    while(x){
        digit[++len]=x%10;
        x/=10;
    }
    return dfs(len,1,1,0,1);
}
int check(int mid){
    return solve(mid)-solve(0);
}
signed main(){
    memset(d,-1,sizeof d);
    int k;
    cin>>k;
    int l=1,r=1e15;
    int ans=-1;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)>=k){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值