算法提高之动态规划:数位dp

1、度的数量

在这里插入图片描述
在这里插入图片描述

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

using namespace std;

const int N = 35;

int K, B;
//从a个数中选b个数的方案数
int f[N][N]; 

void init()
{
	f[0][0]=1;
    for (int i = 1; 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;
	//将边界 l-1,r 处理成b进制数 
    vector<int> nums;
    while (n) nums.push_back(n % B), n /= B;
	//记录满足情况的数据
    int res = 0;
    //假设枚举到了i-2位,i和i-1位中有几个1,由last记录
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        //x是否>0
        if (x)  // 求左边分支中的数的个数
        {
        //大于0就将i位为0的情况记录下来 
        //由于i=0,i-1~0 总共由i个数据 所以从i个数据中选
            res += f[i][K - last];
        //当x>1时,第i位可以为1
            if (x > 1)
            {
            	//判断k是否装满
            	//i个数据,k-last-1 是由于i位填写需要减1
                if (K - last - 1 >= 0) res += f[i][K - last - 1];
                //因为题目要求是由k个互不相等的b的整数次幂之和
                //数据表示成b进制只能含0,1 不能含 2~b-1
                //因此x>1只包含x=1一种情况
                break;
            }
            //x=1 需要判断右分支
            else
            {
                last ++ ;
                //last超过k就结束循环
                if (last > K) break;
            }
        }
		//特殊处理右分支的最后一个数 !i 最后一个数  last==k 是恰好装满k
		//x=1,x>1的情况在以上分支被处理,x=0满足题目要求。
		//因为题目要求是由k个互不相等的b的整数次幂之和
        //数据表示成b进制只能含0,1 不能含 2~b-1
        if (!i && last == K) res ++ ;   // 最右侧分支上的方案
    }

    return res;
}

int main()
{
    init();

    int l, r;
    cin >> l >> r >> K >> B;

    cout << dp(r) - dp(l - 1) << endl;

    return 0;
}

2、数字游戏

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

using namespace std;

//2^31 是20亿,一共10位数,写成15位正合适
const int N = 15;

int f[N][N];    // f[i, j]表示一共有i位,且最高位填j的数的个数

void init()
{
	//共1位,无论首位是几,都是1方案
    for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;
	//从2位开始枚举
    for (int i = 2; i < N; i ++ )
    	//首位从0到9
        for (int j = 0; j <= 9; j ++ )
        	//k是枚举第二位是几
            for (int k = j; k <= 9; k ++ )
                f[i][j] += f[i - 1][k];
}

int dp(int n)
{
	//特判n为0,也是一种方案
    if (!n) return 1;
	//将数据拆开
    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;
	//记录结果
    int res = 0;
    //记录前一位是几
    int last = 0;
    //诸位枚举
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        //左侧计算 枚举最高为填last~an-1的方案
        for (int j = last; j < x; j ++ )
            res += f[i + 1][j];
		//右侧计算 判断当前一位能否为x 前一位比x大时 不行
        if (x < last) break;
        last = x;
		//判断最右侧分支,以上代码已将左最一层左侧分支计算完毕
		//并且保证每一个数都是大于等于前一个数
        if (!i) res ++ ;
    }

    return res;
}

int main()
{
    init();

    int l, r;
    while (cin >> l >> r) cout << dp(r) - dp(l - 1) << endl;

    return 0;
}

3、Windy数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

using namespace std;
//最多的位数 总共有10位
const int N = 11;
//一共有i位,且最高位是j
int f[N][10];

void init()
{
//f[2][2] 一共有2位,最高位是2.f[1][0]=1时,f[2][2]中的f[2][0]才能迭代
    for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;

    for (int i = 2; i < N; i ++ )
    	//j是高位
        for (int j = 0; j <= 9; j ++ )
        //k是高位的下一位
            for (int k = 0; k <= 9; k ++ )
                if (abs(j - k) >= 2)
                    f[i][j] += f[i - 1][k];
}



int dp(int n)
{
//0不是windy,因为0不在题目范围内
    if (!n) return 0;

    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    //为了让首位数字任意填,让首位数和0~9之间的任意数的差都>=2的数
    int last = -2;
    
    //循环是处理与最高位数字相关联的windy数
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        //枚举左边的分支 
        //j不是每一次都可以从0开始算
        //当j是最高位的时候,只能从1开始 
        for (int j = i == nums.size() - 1; j < x; j ++ )
            if (abs(j - last) >= 2)
            	//从i位到0位 i+1位数
                res += f[i + 1][j];

        if (abs(x - last) >= 2) last = x;
        else break;
		//右最后一个分支
        if (!i) res ++ ;
    }
	//低于n位的数,需要特判
    // 特殊处理有前导零的数
    //从第一位开始枚举

    
    //这一层特判是叠加比最高位位数低的windy数
    //例如1000 有四位数 则3位和2位以及1位所有windy数均在范围内
    for (int i = 1; i < nums.size(); i ++ )
    //枚举最高位填多少 由于是i位数,最高位不填0 
        for (int j = 1; j <= 9; j ++ )
            res += f[i][j];

    return res;
}

int main()
{
    init();

    int l, r;
    cin >> l >> r;
    cout << dp(r) - dp(l - 1) << endl;

    return 0;
}

4、数字游戏2

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

using namespace std;
//最多10位数  N表示位数 M表示取模的数字
const int N = 11, M = 110;
//取模
int P;
//f[i][j][k] 一共有i位,最高位是j,各位之和相对于M的余数是k
int f[N][10][M];
//c++取模比较特殊 负数取模是负数,整数取模是正数
//现在是想让负数和正数都取模变成正数
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] 当有i位数,最高位是j,且余数是k
        //f[i-1][x][mod(k-j,P)] 当有i-1位数,最高位是x,且余数是
        //(k-j)含有最高位在内的所有位数之和取余的余数减去最高位 再取模得到
        //包含次高位在内的所有余数之和。也就是通过这个处理建立了最高位数据和次高位数据之间的关系
                    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;
    //前边各位数字之和
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        //处理左边分支
        int x = nums[i];
        for (int j = 0; j < x; j ++ )
        //通过mod(-last,P)赖筛选需要相加的数据。
            res += f[i + 1][j][mod(-last, P)];
		//右边分支	
        last += x;
		//右侧分支最后一个数 ,如果是最后一个数,last已经将所有的数据累加完毕
        if (!i && last % P == 0) res ++ ;
    }

    return res;
}

int main()
{
    int l, r;
    while (cin >> l >> r >> P)
    {
        init();

        cout << dp(r) - dp(l - 1) << endl;
    }

    return 0;
}

5、不要62

在这里插入图片描述
在这里插入图片描述

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

using namespace std;

const int N = 10;

int f[N][10];

void init()
{
    for (int i = 0; i <= 9; i ++ )
        if (i != 4)
            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;
    int 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;
        last = x;

        if (!i) res ++ ;
    }

    return res;
}

int main()
{
    init();

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

    return 0;
}

6、恨7不成妻

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此处合法数是i-1位
在这里插入图片描述
此处的合法数是i位
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

using namespace std;

typedef long long LL;
//N表示位数
const int N = 20, P = 1e9 + 7;

//f[i][j][a][b] 一共由i位数,最高位是j,数本身模7是a,所有数位之和模7是b
struct F
{
//含有i位的状态方程推出i-1位的状态方程
//s0 i-1位中合法状态数据个数 s1 i-1位中合法状态数据和  
//s2 i-1位中合法状态数据平方和
    int s0, s1, s2;
}f[N][10][7][7];
//10的多少幂模7  10的多少次幂模p的结果
int power7[N], power9[N];

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

void init()
{
    for (int i = 0; i <= 9; i ++ )
    {
        if (i == 7) continue;
        auto& v = f[1][i][i % 7][i % 7];
        v.s0 ++, v.s1 += i, v.s2 += i * i;
    }

    LL power = 10;
    for (int i = 2; i < N; i ++, power *= 10)
        for (int j = 0; j <= 9; j ++ )
        {
            if (j == 7) continue;
            for (int a = 0; a < 7; a ++ )
                for (int b = 0; b < 7; b ++ )
                    for (int k = 0; k <= 9; k ++ )
                    {
                        if (k == 7) continue;
                        auto &v1 = f[i][j][a][b], &v2 = f[i - 1][k][mod(a - j * power, 7)][mod(b - j, 7)];
                        v1.s0 = mod(v1.s0 + v2.s0, P);
                        v1.s1 = mod(v1.s1 + v2.s1 + j * (power % P) % P * v2.s0, P);
                        v1.s2 = mod(v1.s2 + j * j * (power % P) % P * (power % P) % P * v2.s0 + v2.s2 + 2 * j * power % P * v2.s1, P);
                    }
        }

    power7[0] = 1;
    for (int i = 1; i < N; i ++ ) power7[i] = power7[i - 1] * 10 % 7;

    power9[0] = 1;
    //ll  long long
    for (int i = 1; i < N; i ++ ) power9[i] = power9[i - 1] * 10ll % P;
}

F get(int i, int j, int a, int b)
{
    int s0 = 0, s1 = 0, s2 = 0;
    for (int x = 0; x < 7; x ++ )
        for (int y = 0; y < 7; y ++ )
            if (x != a && y != b)
            {
                auto v = f[i][j][x][y];
                s0 = (s0 + v.s0) % P;
                s1 = (s1 + v.s1) % P;
                s2 = (s2 + v.s2) % P;
            }
    return {s0, s1, s2};
}

int dp(LL n)
{
    if (!n) return 0;
	//原数
    LL backup_n = n % P;
    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    //a前边数的本身   b前边数的各位数之和
    LL last_a = 0, last_b = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        for (int j = 0; j < x; j ++ )
        {
            if (j == 7) continue;
            int a = mod(-last_a * power7[i + 1], 7);
            int b = mod(-last_b, 7);
            //题目的性质
            //一共i+1位,最高位是j,且模7后,不为a,不为b
            auto v = get(i + 1, j, a, b);
            res = mod(
                res + 
                (last_a % P) * (last_a % P) % P * power9[i + 1] % P * power9[i + 1] % P * v.s0 % P + 
                v.s2 + 
                2 * last_a % P * power9[i + 1] % P * v.s1,
            P);
        }

        if (x == 7) break;
        last_a = last_a * 10 + x;
        last_b += x;
		//last_a%7!=0
        if (!i && last_a % 7 && last_b % 7) res = (res + backup_n * backup_n) % P;
    }

    return res;
}

int main()
{
    int T;
    cin >> T;

    init();

    while (T -- )
    {
        LL l, r;
        cin >> l >> r;
        cout << mod(dp(r) - dp(l - 1), P) << endl;
    }

    return 0;
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值