算法基础课—动态规划(三)计数类DP、数位统计DP、状态压缩DP、树形DP、记忆化搜索

数位统计DP

多用于统计数中出现某个数的次数
关键:分类讨论

题目

计数问题

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…

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

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

当读入一行为 0 0 时,表示输入终止,且该行不作处理。

输出格式
每组数据输出一个结果,每个结果占一行。

每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。

数据范围
0<a,b<100000000
输入样例:
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出样例:
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

思想

如果采用正常的常规操作,一个个去枚举,再去拆分,再去计算,时间复杂度较高,于是考虑采取一种较为简便的方法。

考虑计算1-n中出现数字 i 的个数,假设n有k位,则数字i有可能在1-k个位置出现,存在在1-n范围内,第j位数字为1的情况可以用排列组合计算出来,于是我们可以计算出数字i在1-k个位置上,产生的1-n范围内的数字的数量,将其相加即为总和。
是否会重复相加?
不会,因为假设2222这个数,每一位都是2,在第1位为2时会被包括进去会加1次,第二位为2时也会被加一次,以此类推,而2222的确是包含4个2,要被加4次,所以正确

思路关键:枚举在1-n范围内,第i位为x的情况有多少种,i = 1-k…

当x != 0
在这里插入图片描述
假设要求x在d位商的情况
第一种情况 前三个数字从000-abc-1 情况有abc*100种
第二种情况 前三个数字等于abc
当d < x 0种情况
当d = x efg + 1种情况
当d > x 时 1000种情况

如果x == 0
n的第一位一定不可能是0,所以从第二位开始遍历
由于前导0 的情况,如果前面都是000,而d 也 等于0 时,这样表示的数是无意义的,因为前导0并不包括在计数内。所以从001开始
在这里插入图片描述
思路关键:
1、碰到求多个区间内的个数啊值啊——很有可能会可以用前缀和,所以问题就简化为从1-n的个数的问题
2、考虑从1到n的个数的问题,就可以用数学的排列组合取思考。

模板

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

using namespace std;

const int N = 10;

/*

001~abc-1, 999

abc
    1. num[i] < x, 0
    2. num[i] == x, 0~efg
    3. num[i] > x, 0~999

*/

int get(vector<int> num, int l, int r)
{
   
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

int power10(int x)
{
   
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)
{
   
    if (!n) return 0;

    vector<int> num;
    while (n)
    {
   
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();

    int res = 0;
    for (int i = n - 1 - !x; i >= 0; i -- )
    {
   
        if (i < n - 1)
        {
   
            res += get(num, n - 1, i + 1) * power10(i);
            if (!x) res -= power10(i);
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if (num[i] > x) res += power10(i);
    }

    return res;
}

int main()
{
   
    int a, b;
    while (cin >> a >> b , a)
    {
   
        if (a > b) swap(a, b);

        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/64211/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

int get(vector<int> num, int l, int r){
   
    int res = 0, t = 1, i;
    for(i = r; i <= l; i ++){
   
        res += t * num[i];
        t *= 10;
    }
    return res;
}
int pow10(int x)
{
   
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}
int count(int n, int x){
   
    if(!n) return 0;//注意这一步
    int i;
    vector <int> num;
    while(n){
   
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();
    int res = 0;
    for(i = n - 1 - !x; i >= 0; i --){
   // 如果x是0,0不可能在第一位,要从第二位开始
        if(i < n - 1){
   
            res += get(num, n - 1, i + 1) * pow10(i);
            if(!x) res -= pow10(i);//注意这里是i,0-i-1有i位数
        }
        if(num[i] == x) res += get(num, i - 1, 0) + 1;
        else if(num[i] > x) res += pow10(i);
    }
    
    return res;
}
int main(){
   
    int i;
    int a, b;
    cin>>a>>b;
    while(a != 0 && b != 0){
   
        if(a > b) swap(a, b); //因为后面求前缀和的部分要求后面的大,而输入格式不一定满足后面的大
        for(i = 0; i <= 9; i ++){
   
            cout<<count(b, i) - count(a - 1, i)<<" ";
        }
        cout<<endl;
        cin>>a>>b;
    }
}

状态压缩DP

二进制表示状态——状态压缩
状态压缩dp——状态表示中,其中有一维是二进制状态
状态压缩问题——n不能太大,n<=20,2的n次方已经是极限了
状态压缩dp中状态表示里有一维为二进制状态表示,另外一维为其他表示
状态计算:
1、他可能由几种状态转变过来
2、假设由k状态转变过来,k状态是否合法(是否与当前状态有冲突,要满足k状态为当前状态前驱的条件)
3、判断题目是求和还是最大或者最小

蒙德里安的梦想

求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:
在这里插入图片描述

输入格式
输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式
每个测试用例输出一个结果,每个结果占一行。

数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值