dp的笔记5(1坑)

1) 数位dp

度的数量

本题的  F[i][j]  数组表示在 i 个位置中选择 j 个位置填上 1 的所有方案的数量
last 表示当前已经确定的 1 的个数

F [ i ] [ j ] = F [ i − 1 ] [ j ] + F [ i − 1 ] [ j − 1 ] F[i][j] = F[i - 1][j] + F[i - 1][j - 1] F[i][j]=F[i1][j]+F[i1][j1]

1.png

2.png

3.png

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 35;

int f[N][N], K, B, l, r;

void init()
{
    for(int i = 0; i < N; i ++)
        for(int j = 0; j <= i; j ++)
            if(!j)  f[i][j] = 1;
            else    f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}

int dp(int n)
{
    if(!n)  return 0;
    
    vector < int > nums;
    while(n)    nums.push_back(n % B), n /= B;
    
    // for(int i = nums.size() - 1; i >= 0; i --)  cout << nums[i] << " ";
    // cout << endl;
    
    int res = 0;
    int last = 0;
    for(int i = nums.size() - 1; i >= 0; i --){
        int x = nums[i];
        if(x){// 如果当前数大于0,也就是后面的位数可以全取1
            res += f[i][K - last];// 当前位取0,也就是后面的i位数加一个组合计数
            if(x > 1){// 当前位的数大于1,也就是当前位加后面的i位数都可以取1
                if(K - last - 1 >= 0) res += f[i][K - last - 1];// 加上当前位取1的组合计数
                break;
            }else{// 当前位是1,后面的i位的数字大小无法确定,也就不是组合计数
                last ++;// 但已经可以确定数是1
                if(last > K)    break;
            }
        }
        if(!i && last == K) res ++;
    }
    
    return res;
}

int main()
{
    cin >> l >> r >> K >> B;
    init();
    
    // for(int i = l; i <= r; i ++)
    //     dp(i);
    
    cout << dp(r) - dp(l - 1) << endl;
    return 0;
}

数字游戏

首先题目要求的是从左到右各位数字呈非下降关系,如 123,446。
然后题目要求的是一段区间[L,R]中的数量
所以还是可以用前缀和的思想,先求出[0,L-1] 和 [0,R]两个区间的数量然后区间[0,R] - 区间[0,L-1]就是答案

然后就是本题的 F[i][j] 数组表示的是一共有 i 位数字,并且最高位是 j 的所有数的集合
last 表示当前遍历到第 i 位, last就是第 i - 1 位的数字大小

12.png
F [ i ] [ j ] = ∑ k = 0 9 F [ i − 1 ] [ k ] ( k > = j ) F[i][j] = \sum_{k=0}^{9}{F[i - 1][k]} (k>=j) F[i][j]=k=09F[i1][k](k>=j)

4.png

5.png

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 15;

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

void init()
{
    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 > num;
    while(n)    num.push_back(n % 10), n /= 10;
    
    int res = 0, last = 0;
    for(int i = num.size() - 1; i >= 0; i --){// 遍历每一位(树一样的那部分)
        int x = num[i];
        
        for(int j = last; j < x; j ++)// 集合的划分部分
            res += f[i + 1][j];
        
        if(last > x)    break;// 判断是否继续
        last = x;// 计算完当前位,将当前位的数赋值给last,
        if(!i)  res ++;// 判断是否进行到最后一位
    }
    
    return res;
}

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

Windy数

首先题目要求的是不含前导零且相邻两个数字之差至少为 2 的正整数  比如 25 26 31  135
然后题目要求的是一段区间[L,R]中的数量
所以还是可以用前缀和的思想,先求出[0,L-1] 和 [0,R]两个区间的数量然后区间[0,R] - 区间[0,L-1]就是答案

然后就是本题的 F[i][j] 数组表示的是一共有 i 位数字,并且最高位是 j 的所有数的集合
last 表示当前遍历到第 i 位, last就是第 i - 1 位的数字大小

11.png
F [ i ] [ j ] = ∑ k = 0 9 F [ i − 1 ] [ k ] ( ∣ k − j ∣ > = 2 ) F[i][j] = \sum_{k = 0}^{9} F[i - 1][k] (|k - j| >= 2) F[i][j]=k=09F[i1][k](kj>=2)

然后这个就是有一个前导0需要注意的,也就是我们不能够在最高位填0这个数字
比如  321  我们不能在 3 这个位置填上 0 因为根据f[i][j]数组的定义这个地方填 0 会丢失 013 014 等等很多数据  
所以我们需要单独的处理前导 0 这个问题,就是在除最高位的所有其他位置可以分别填上 1~9 
上面那个题目(数字游戏)可以包含前导 0
比如 321 在最高位 3 上填 0 依然不会丢失 003  012(这样也是满足条件) 这样的数据

6.png

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 15;

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

void init()
{
    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 = 0; k <= 9; k ++)
                if(abs(k - j) >= 2)
                    f[i][j] += f[i - 1][k];
}

int dp(int n)
{
    if(!n)  return 0;
    
    vector < int > nums;
    while(n)    nums.push_back(n % 10), n /= 10;
    
    int res = 0, last = -2;
    for(int i = nums.size() - 1; i >= 0; i --){
        int x = nums[i];
        
        for(int j = i == nums.size() - 1; j < x; j ++)// 集合划分从 0(1) 到 x - 1
            if(abs(last - j) >= 2)
                res += f[i + 1][j];
        
        if(abs(last - x) < 2)   break;// 判断是否满足条件
        last = x;
        
        if(!i)  res ++;// 遍历到最后一位
    }
    for(int i = 1; i < nums.size(); i ++)// 最高位取0时的情况
        for(int j = 1; j <= 9; j ++)
            res += f[i][j];
    
    return res;
}

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

不要62

首先题目要求的是数字中不包含4且不包含连续的62的数字 比如24 162都是不满足条件的
然后题目要求的是一段区间[L,R]中的数量
所以还是可以用前缀和的思想,先求出[0,L-1] 和 [0,R]两个区间的数量然后区间[0,R] - 区间[0,L-1]就是答案

然后就是本题的 F[i][j] 数组表示的是一共有 i 位数字,并且最高位是 j 的所有数的集合
last 表示当前遍历到第 i 位, last就是第 i - 1 位的数字大小

10.png
F [ i ] [ j ] = ∑ k = 0 9 F [ i − 1 ] [ k ] ( j ! = 4 ) ( k ! = 4 ) ( j ! = 6 同 时 k ! = 2 ) F[i][j] = \sum_{k=0}^{9} F[i - 1][k] (j != 4)(k != 4)(j != 6 同时 k != 2) F[i][j]=k=09F[i1][k](j!=4)(k!=4)(j!=6k!=2)

然后这个题目时可以填上前导0的,因为 023 是满足条件的

7.png

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 15;

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

void init()
{
    for(int i = 0; i <= 9; i ++){
        if(i == 4)  continue;
            f[1][i] = 1;
    }
    for(int i = 2; i < N; i ++)
        for(int j = 0; j <= 9; j ++){
            if(j == 4)  continue;
            for(int k = 0; k <= 9; k ++){
                if(k == 4 || (j == 6 && k == 2))    continue;
                f[i][j] += f[i - 1][k];
            }
        }
}

int dp(int n)
{
    if(!n)  return 1;
    
    vector < int > nums;
    while(n)    nums.push_back(n % 10), n /= 10;
    
    int res = 0, last = 0;
    for(int i = nums.size() - 1; i >= 0; i --){// 遍历每一位
        int x = nums[i];
        
        for(int j = 0; j < x; j ++){ // 集合划分的部分
            if(j == 4 || (last == 6 && j == 2)) continue;
            res += f[i + 1][j];
        }
        
        if(x == 4 || (last == 6 && x == 2)) break;// 如果出现4 或者连续的62则退出
        last = x;// last 表示前一位的数字
        if(!i)  res ++;
    }
    return res;
}

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

数字游戏 II

首先题目要求的是一个数中每个数字加起来模 N 等于 0 的数 比如 N = 9 时,9,18,27 都是满足条件的
然后题目要求的是一段区间[L,R]中的数量
所以还是可以用前缀和的思想,先求出[0,L-1] 和 [0,R]两个区间的数量然后区间[0,R] - 区间[0,L-1]就是答案

然后就是本题的 F[i][j][k] 数组表示的是一共有 i 位数字,最高位是 j 并且当前 i 位数字的和为 K (%P的情况下)的所有数的集合
last 表示当前遍历到第 i 位, last就是前 i - 1 位的数字的和

8.png
F [ i ] [ j ] [ k ] = ∑ x = 0 9 F [ i − 1 ] [ x ] [ ( k − j ) % P ] ( 0 < = j < = 9 ) ( 0 < = k < P ) F[i][j][k] = \sum_{x=0}^{9} F[i - 1][x][(k - j) \% P] (0 <= j <= 9) (0 <= k < P) F[i][j][k]=x=09F[i1][x][(kj)%P](0<=j<=9)(0<=k<P)

首先就是 a + b ≡ 0(mod N),同余等式两边是可以移项的,所以就有 a ≡ −b(mod N)。
然后就是从高到低枚举每一位时
当我在第 i 位填数字时,如何才能保证我们填上数字之后,所有的数字之和加起来(此时并不确定后面填的啥数字) % P == 0
这个时候 last(表示当前遍历到第 i 位,last就是前 i - 1 位的数字的和)就能用上,首先我们假设第 i 位到最低位的和是 X 
此时的 last + x 就是所有位的数字和
然后就是 last + x ≡ 0(mod P),同余等式两边是可以移项的,所以就有 x ≡ −last(mod P)。
此时我们就可以用 -last 来表示 x (此时我们不确定后面填的啥数字,但我们确定了后面填的数字的和)
也即我们就可以用我们的状态 F[ i ][ j ][ - last ] 一共有 i 位数字,最高位是 j 并且当前 i 位数字的和为 -last (%P的情况下)的所有数的集合

9.png

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 15, M = 110;

int f[N][N][M], l, r, p;

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

void init()
{
    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 > nums;
    while(n)    nums.push_back(n % 10), n /= 10;
    
    int res = 0, last = 0;
    for(int i = nums.size() - 1; i >= 0; i --){
        int x = nums[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){
        init();
        
        cout << dp(r) - dp(l - 1) << endl;
    }
    
    return 0;
}

恨7不成妻

emmm… 下次补把

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值