数位dp小结 题目由易到难 20寒假

这次不这么注重格式 还搞什么超链接
简单粗暴 题+题解
目录:
1、不要62 HDU2089(同题型Bomb 不再整理
最基础数位dp!
2、B-Number HDU3652
稍微进阶 多个 各个位之和可被整除
3、Balanced Number HDU3709 (枚举所有支点 进行搜索
4、吉哥故事系列——恨7不成妻 HDU4507
更难一点 约定条件下 各个数字的平方和 用到了完全平方式
5、Final Kichiku “Lanlanshu” HDU3886
求符合升降趋势的串有多少个 存在一个就行
——————————————————————————————————————

1、不要62

基础数位dp
问你区间内多少数不带连续的“62”
不多说 基础dp 套板子即可
其中dp i j 代表dp到第i位时 前面有或无6 所满足条件的个数
这里避免冗余 只放核心代码(下同)

int dig[maxn];
ll dp[maxn][2];//第二维判断当前是否为

ll dfs(int pos,bool if6,bool limit){
    if(!pos) return 1;
    //如果不是上限并且这个已经求出来过了
    if(!limit&&dp[pos][if6]) return dp[pos][if6];
    int up=limit?dig[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(i==4) continue;
        if(if6&&i==2) continue;
        ans+=dfs(pos-1,i==6,limit&&i==up);
    }
    if(!limit) dp[pos][if6]=ans;
    return ans;
}

2、B-number HDU3652

问你区间内多少数含“13”且各个位数之和能被13整除
数位dp板子加上一维 记录三种状况:前面已经有13,前面只有1 前面都没有
dp i j k 代表到第i位时 到当前这位取模为mod时 情况为k时 满足条件的个数
注意这题有一点点不一样是 区间内为0的居多 所以吸取教训 dp数组最好不要初始化为0 不然就算已经统计过 还按没统计过重新统计 所以初始化为-1最好!
核心代码如下:

const int maxn=20;
int dig[maxn];
int dp[maxn][maxn][3];//dp i j k  代表取模后是多少 i代表截止第i位 j代表到i位模13余j k代表13情况
int dfs(int pos,int mod,bool con,bool if1,bool limit){
    //pos当前第几位 之前数取模后余多少 con之前有无13 if1上一位是否为1 limit上一位是否为上界
    if(!pos) return !mod&&con;//取模为0且有13了
    if(!limit){//若上一位不是上界
        if(con){//若已经有了13
            if(~dp[pos][mod][2]) return dp[pos][mod][2];
        }else{//若前面还没有13
            if(if1&&~dp[pos][mod][1]) return dp[pos][mod][1];
            else if(!if1&&~dp[pos][mod][0]) return dp[pos][mod][0];
        }
    }
    int up=limit?dig[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,(mod*10+i)%13,con||(if1&&i==3),i==1,limit&&i==up);
    }
    if(!limit){//若上一位不是上界
        if(con){//若已经有了13
            if(dp[pos][mod][2]==-1) dp[pos][mod][2]=ans;
        }else{//若前面还没有13
            if(if1&&dp[pos][mod][1]==-1) dp[pos][mod][1]=ans;
            else if(!if1&&dp[pos][mod][0]==-1) dp[pos][mod][0]=ans;
        }
    }
    return ans;
}

3、Balanced Number HDU3709

这题问的是区间内有多少数是平衡数
平衡数定义:以某个数为支点,其他位上的数乘以到这个数的距离 左边的减去右边的 如果是0 就证明是平衡数
dp i j k代表当处理到第i位时 支点为第j位时 权重为k时 满足条件的数量 还是满足的少 所以初始化dp为-1
这里还要考虑重复的0
因为比如一个数有五位 那会统计五个0 所以要减去(pos-1)个0

ll dig[maxn];
ll dp[maxn][maxn][3000];
ll dfs(int pos,int x,ll val,bool limit){
    if(!pos) return !val;//0则1  非0则0
    if(val<0) return 0;//重要剪枝!也防止越界!
    if(!limit&&~dp[pos][x][val]) return dp[pos][x][val];
    int up=limit?dig[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,x,val+i*(pos-x),limit&&i==up);
    }
    if(!limit&&dp[pos][x][val]==-1) dp[pos][x][val]=ans;
    return ans;
}
ll solve(ll num){
    int pos=0;//这里没初始化!!!
    while(num){
        dig[++pos]=num%10;
        num/=10;
    }
    ll ans=0;
    //枚举支点
    for(int i=1;i<=pos;i++){
        ans+=dfs(pos,i,0,true);
    }
    //会出现重复的0的情况!!!因为枚举了好多次嘛  所以枚举了k次 就多了k-1个0 要减去!
    return ans-pos+1;
}

4、吉哥故事系列——恨7不成妻

这题真是有点难度1551 看了一天才看明白
一般的数位dp是统计数量
再升级一点 统计数字之和
这个更难了 统计数字平方和 还要限定条件 不满足的不行
大体思路就是 dp数组不简单是统计数量了 除了数量num还要统计区间内数之和 数平方和
所以建议存node数组里 dfs返回值也是node类型
而dp i j k 存的是 到第i位时 各个位数字和mod7为j 该数mod7为k时 满足条件的数量
下面是核心代码 值得好好琢磨 这里先去掉取模!!!
其实想一想很简单
就是说 举个例子 我们求 355的平方怎么求?
(355) ^ 2=(300+55) ^ 2 = 300^ 2 +55^2 +230055
发现了吗!后面的三项其实就是完全平方式的展开
其中第一项就相当于当前这一位 就相当于 iip[pos]*p[pos]
然后第二项就是temp.sum2
然后第三项就是两倍的两边之积
又因为第一项一般而言不止一个(应该是有temp.cnt个 意思是后面满足条件的个数个 所以前面应该乘以temp.cnt
所以核心代码如下:p[i]记录的是10的i次方mod1e9+7 防止爆

node dfs(int pos,int sum1,int sum2,bool limit){
    if(pos<0){
        node temp;
        temp.num=(sum1!=0&&sum2!=0);//意思是只有全不!为!0!才算1个 写错了!!
        temp.sum1=temp.sum2=0;
        return temp;
    }
    if(!limit&&~dp[pos][sum1][sum2].num) return dp[pos][sum1][sum2];
    int up=limit?dig[pos]:9;
    node ans;
    ans.num=ans.sum1=ans.sum2=0;
    for(int i=0;i<=up;i++){
        if(i==7) continue;
        node temp=dfs(pos-1,(sum1+i)%7,(sum2*10+i)%7,limit&&i==up);
        ans.num+=temp.num;
        ans.sum1+=(temp.sum1+temp.num*i*p[pos]);
        ans.sum2+=(temp.sum2+2*temp.sum1*i*p[pos]);
        ans.sum2+=(temp.num*i*i*p[pos]*p[pos]);
    }
    if(!limit) dp[pos][sum1][sum2]=ans;
    return ans;
}

完整代码如下:

const int maxn=22;
const ll mod=1000000007;
struct node{
    ll num,sum1,sum2;
}dp[maxn][7][7];
int dig[maxn];
ll p[maxn];
node dfs(int pos,int sum1,int sum2,bool limit){
    if(pos<0){
        node temp;
        temp.num=(sum1!=0&&sum2!=0);//意思是只有全不!为!0!才算1个 写错了!!
        temp.sum1=temp.sum2=0;
        return temp;
    }
    if(!limit&&~dp[pos][sum1][sum2].num) return dp[pos][sum1][sum2];
    int up=limit?dig[pos]:9;
    node ans;
    ans.num=ans.sum1=ans.sum2=0;
    for(int i=0;i<=up;i++){
        if(i==7) continue;
        node temp=dfs(pos-1,(sum1+i)%7,(sum2*10+i)%7,limit&&i==up);
        ans.num+=temp.num;
        ans.num%=mod;
        ans.sum1+=(temp.sum1+temp.num*i%mod*p[pos]%mod)%mod;
        ans.sum1%=mod;
        ans.sum2+=(temp.sum2+2*temp.sum1%mod*i*p[pos]%mod)%mod;
        ans.sum2%=mod;
        ans.sum2+=(temp.num*i*i%mod*p[pos]%mod*p[pos]%mod)%mod;
        ans.sum2%=mod;
        //你加这句能过么????????????
        //printf("%lld %lld %lld\n",ans.num,ans.sum1,ans.sum2);
    }
    if(!limit) dp[pos][sum1][sum2]=ans;
    return ans;
}
ll solve(ll num){
    int pos=0;
    while(num){
        dig[pos++]=num%10;
        num/=10;
    }
    return dfs(pos-1,0,0,true).sum2;
}
int main() {
    p[0]=1;//应该是p0=1; 而不是p1=0; 我服了!
    //这里忘初始化了!!!
    for(int i=1;i<maxn;i++) p[i]=10*p[i-1]%mod;
    for(int i=0;i<maxn;i++)
    for(int j=0;j<7;j++)
    for(int k=0;k<7;k++)
        dp[i][j][k].num=-1;
    int T;
    scanf("%d",&T);
    while(T--){
        ll l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",(solve(r)-solve(l-1)+mod)%mod);
    }
    //system("pause");
	return 0;
}

5、Final Kichiku “Lanlanshu”

这题也是 给你/–//\ 这样的大小关系 (注意是-不是=!!!
问你所给区间范围内满足这样大小关系的数有多少
核心代码如下:

int dfs(int pos,int rpos,int pre,bool pre0,bool limit){
    if(!pos) return rpos==rlen+1;//只有前面有一组数都符合了就行!
    //还要没有前导0...
    if(!limit&&!pre0&&~dp[pos][rpos][pre]) return dp[pos][rpos][pre];
    int up=limit?dig[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        //注意下面这个if 不需要pre0&&i==0  意思是i!=0也可以实现
        if(pre0) ans+=dfs(pos-1,1,i,i==0,limit&&i==up);
        else{//前面不是0 意思是至少是第二位了!!!所以是一定可以判断的
            if(rpos<=rlen&&check(pre,i,r[rpos])) 
                ans+=dfs(pos-1,rpos+1,i,false,limit&&i==up);
            //上面是可以正常往下走的情况 不然就看看当前这两个是否满足上一个关系符号 满足就用这个符号往下走  递推过程吧
            else if(rpos>1&&check(pre,i,r[rpos-1])) 
                ans+=dfs(pos-1,rpos,i,false,limit&&i==up);
        }
        ans%=mod;
    }
    if(!limit&&!pre0) dp[pos][rpos][pre]=ans;
    return ans;
}
const int maxn=110;
const int mod=100000000;
int rlen,slen;//关系长度 数字长度
char r[maxn];//大小关系数组
char s[maxn];//数字字符数组
int dig[maxn];//数字整数数组
int dp[maxn][maxn][10];//dp[i][j][k]代表数字处理到第i位时 大小关系处理到第j位时 前面的数字为k的情况下 满足条件的情况
bool check(int a,int b,char c){
    if(c=='/') return a<b;
    else if(c=='-') return a==b;//这里不是=!!!是-!!!我服了!!!
    return a>b;
}
int dfs(int pos,int rpos,int pre,bool pre0,bool limit){
    //printf("%d %d %d\n",pos,rpos,pre);
    if(!pos) return rpos==rlen+1;//只有前面有一组数都符合了就行!
    //还要没有前导0...
    if(!limit&&!pre0&&~dp[pos][rpos][pre]) return dp[pos][rpos][pre];
    int up=limit?dig[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        //注意下面这个if 不需要pre0&&i==0  意思是i!=0也可以实现
        if(pre0) ans+=dfs(pos-1,1,i,i==0,limit&&i==up);
        else{//前面不是0 意思是至少是第二位了!!!所以是一定可以判断的
            if(rpos<=rlen&&check(pre,i,r[rpos])) 
                ans+=dfs(pos-1,rpos+1,i,false,limit&&i==up);
            //上面是可以正常往下走的情况 不然就看看前一个关系符号1和当前这个3符合不符合了 就忽视掉中间这个2了!
            else if(rpos>1&&check(pre,i,r[rpos-1])) 
                ans+=dfs(pos-1,rpos,i,false,limit&&i==up);
        }
        ans%=mod;
    }
    if(!limit&&!pre0) dp[pos][rpos][pre]=ans;
    return ans;
}
int solve(bool sub1_flag){
    int pos=0;//很重要
    int st=1;
    while(s[st]=='0'&&st<=slen) st++;
    if(st==slen+1) return 0;
    for(int i=slen;i>=st;i--){
        dig[++pos]=s[i]-'0';
    }
    if(sub1_flag){//若需要-1
        for(int i=1;i<=pos;i++){
            if(dig[i]==0) dig[i]=9;//这里不是==!!!
            else{
                dig[i]--;
                break;
            }
        }
    }
    if(dig[pos]==0) pos--;
    return dfs(pos,1,0,true,true);
}
int main() {
    while(scanf("%s",r+1)!=EOF){
        cl(dp,-1);
        rlen=strlen(r+1);
        //计算左端点-1
        scanf("%s",s+1);
        slen=strlen(s+1);
        int ans1=solve(true);
        //计算右端点
        scanf("%s",s+1);
        slen=strlen(s+1);
        int ans2=solve(false);
        printf("%08d\n",((ans2-ans1)%mod+mod)%mod);
    }
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值