DP - 数位DP -数字游戏(Ⅰ+Ⅱ)

DP - 数位DP -数字游戏(Ⅰ+Ⅱ)


1、数字游戏Ⅰ

科协里最近很流行数字游戏。

某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。

现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。

输入格式
输入包含多组测试数据。

每组数据占一行,包含两个整数 a 和 b。

输出格式
每行给出一组测试数据的答案,即 [a,b] 之间有多少不降数。

数据范围
1≤a≤b≤231−1

输入样例:
1 9
1 19
输出样例:
9
18

分析:

d p 函 数 求 1 dp函数求1 dp1~ N 中 不 降 数 的 个 数 。 N中不降数的个数。 N

首 先 将 N 每 一 位 存 入 数 组 V = a n − 1 a n − 2 . . . a 0 , 首先将N每一位存入数组V=a_{n-1}a_{n-2}...a_{0}, NV=an1an2...a0

从 高 位 到 低 位 [ n − 1 , 0 ] 依 次 遍 历 : 从高位到低位[n-1,0]依次遍历: [n1,0]

设 f [ i , j ] 表 示 长 度 为 i , 且 最 高 位 为 j 的 数 内 , 不 降 数 的 个 数 。 设f[i,j]表示长度为i,且最高位为j的数内,不降数的个数。 f[i,j]ij
则 f [ i , j ] + = f [ i − 1 , k ] , 其 中 k > = j 。 这 里 得 到 f 数 组 的 转 移 方 程 。 则f[i,j]+=f[i-1,k],其中k>=j。这里得到f数组的转移方程。 f[i,j]+=f[i1,k]k>=jf
l a s t 表 示 上 一 位 数 的 大 小 。 last表示上一位数的大小。 last

考 虑 第 i 位 数 a i 考虑第i位数a_i iai

① 、 若 l a s t < = a i < V i , 则 无 论 a i a i − 1 . . . a 0 取 何 值 , 均 不 会 超 过 V , 总 方 案 数 r e s + = f [ i + 1 ] [ j ] 。 ①、若last<=a_i<V_i,则无论a_{i}a_{i-1}...a_0取何值,均不会超过V,总方案数res+=f[i+1][j]。 last<=ai<Viaiai1...a0Vres+=f[i+1][j]

② 、 若 l a s t > V i , 此 时 无 论 如 何 都 无 法 选 出 合 法 方 案 , 直 接 退 出 循 环 , 否 则 更 新 l a s t = V i 。 ②、若last>V_i,此时无论如何都无法选出合法方案,直接退出循环,否则更新last=V_i。 last>Vi退last=Vi

③ 、 若 i = 0 , 即 考 虑 到 最 后 一 位 a 0 , 还 要 加 上 V 本 身 这 个 方 案 。 ③、若i=0,即考虑到最后一位a_0,还要加上V本身这个方案。 i=0a0V

代码:

#include<iostream>
#include<vector>

using namespace std;

const int N=15;

int f[N][N];

void cal()
{
    for(int i=0;i<=9;i++) f[1][i]=1;
    
    for(int i=2;i<N;i++)
        for(int j=0;j<=9;j++)
            for(int k=j;k<=9;k++)
                f[i][j]+=f[i-1][k];
}

int dp(int n)
{
    if(!n) return 1;
    vector<int> V;
    while(n) V.push_back(n%10),n/=10;
    
    int res=0,last=0;
    for(int i=V.size()-1;i>=0;i--)
    {
        int x=V[i];
        for(int j=last;j<x;j++)
            res+=f[i+1][j];
            
        if(x<last) break;
        last=x;
        
        if(!i) res++;
    }
    
    return res;
}

int main()
{
    cal();
    
    int l,r;
    while(cin>>l>>r) cout<<dp(r)-dp(l-1)<<endl;
    
    return 0;
}

2、数字游戏Ⅱ

由于科协里最近真的很流行数字游戏。

某人又命名了一种取模数,这种数字必须满足各位数字之和 mod P 为 0。

现在大家又要玩游戏了,指定一个整数闭区间 [a.b],问这个区间内有多少个取模数。

输入格式
输入包含多组测试数据,每组数据占一行。

每组数据包含三个整数 a,b,P。

输出格式
对于每个测试数据输出一行结果,表示区间内各位数字和 mod P 为 0 的数的个数。

数据范围
1≤a,b≤231−1,
1≤P<100

输入样例:
1 19 9
输出样例:
2

分析:

与 第 一 题 类 似 的 , 与第一题类似的,

把 N 的 每 一 位 存 入 数 组 , 把N的每一位存入数组, N

从 高 位 到 低 位 依 次 遍 历 每 一 位 数 , 从高位到低位依次遍历每一位数,

设 f [ i , j , k ] 表 示 长 度 为 i , 末 尾 数 字 为 j , 且 各 位 数 字 之 和 对 P 取 模 的 余 数 是 k 的 合 法 方 案 总 数 。 设f[i,j,k]表示长度为i,末尾数字为j,且各位数字之和对P取模的余数是k的 合法方案总数。 f[i,j,k]ijPk

形 如 : 形如:
在这里插入图片描述
由 ( j + x + S ) % P = k 得 ( x + S ) % P = ( k − j ) % P , 则 f [ i ] [ j ] [ k ] 可 由 f [ i − 1 ] [ x ] [ ( k − j ) % P ] 转 移 而 来 。 由(j+x+S)\%P=k得(x+S)\%P=(k-j)\%P,则f[i][j][k]可由f[i-1][x][(k-j)\%P]转移而来。 (j+x+S)%P=k(x+S)%P=(kj)%Pf[i][j][k]f[i1][x][(kj)%P]

即 f [ i ] [ j ] [ k ] + = f [ i − 1 ] [ x ] [ ( k − j ) % P ] , 求 f 数 组 的 转 移 方 程 。 即f[i][j][k]+=f[i-1][x][(k-j)\%P],求f数组的转移方程。 f[i][j][k]+=f[i1][x][(kj)%P]f

注意: 负 数 取 模 。 负数取模。

设 l a s t = V n − 1 + V n − 2 + . . . + V i + 1 , 第 i 位 数 填 j 设last=V_{n-1}+V_{n-2}+...+V_{i+1},第i位数填j last=Vn1+Vn2+...+Vi+1ij

① 、 若 j < V i , 无 论 S a = a i a i − 1 . . . a 0 这 i + 1 位 填 什 么 数 , 都 不 会 超 过 V 。 又 因 为 需 满 足 总 和 ( S a + l a s t ) % P = 0 , 故 S a 对 P 取 模 的 余 数 应 当 是 ( − l a s t ) % P 。 总 方 案 数 r e s + = f [ i + 1 ] [ j ] [ ( − l a s t ) % P ] 。 ①、若j<V_i,无论S_a=a_{i}a_{i-1}...a_{0}这i+1位填什么数,都不会超过V。\\\qquad又因为需满足总和(S_a+last)\%P=0,故S_a对P取模的余数应当是(-last)\%P。\\\qquad总方案数res+=f[i+1][j][(-last)\%P]。 j<ViSa=aiai1...a0i+1V(Sa+last)%P=0SaP(last)%Pres+=f[i+1][j][(last)%P]

② 、 更 新 l a s t , 若 枚 举 到 最 后 一 位 a 0 , 且 总 和 l a s t % P = 0 , 还 要 加 上 V 本 身 这 一 个 方 案 。 ②、更新last,若枚举到最后一位a_0,且总和last\%P=0,还要加上V本身这一个方案。 lasta0last%P=0V

代码:

#include<iostream>
#include<vector>
#include<cstring>

using namespace std;

const int N = 11;

int f[N][N][110];
int l,r,P;

int mod(int x,int p)
{
    return (x%p+p)%p;
}

void cal()
{
    memset(f,0,sizeof f);
    
    for(int i=0;i<=9;i++) f[1][i][i%P]++;
    
    for(int i=2;i<N;i++)
        for(int j=0;j<=9;j++)
            for(int k=0;k<P;k++)
                for(int x=0;x<=9;x++)
                    f[i][j][k]+=f[i-1][x][mod(k-j,P)];
}

int dp(int n)
{
    if(!n) return 1;
    
    vector<int> V;
    while(n) V.push_back(n%10),n/=10;
    
    int res=0,last=0;
    for(int i=V.size()-1;i>=0;i--)
    {
        int x=V[i];
        for(int j=0;j<x;j++)
            res+=f[i+1][j][mod(-last,P)];
        
        last+=x;
        
        if(!i && last%P==0) res++;
    }
    
    return res;
}

int main()
{
    while(cin>>l>>r>>P)
    {
        cal();
        cout<<dp(r)-dp(l-1)<<endl;
    }
    
    return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值