数位dp学习笔记

感觉数位dp是我学的dp里面好学一点的了

试题链接:
kuangbin数位dp练习赛

对我做过的题进行总结,整理模板;

数位dp适合的题型:

求出在给定区间[A,B]内,符合条件P(i)的数i的个数.

条件P(i)一般与数的大小无关,而与 数的组成 有关.
总结:
1.如果题目中出现求满足区间[l,r]的符合……性质的数的个数,考虑使用数位dp.
2.思考一下:如果我们只能从前往后一位位枚举当前的数位,要做出这道题,我们需要知道哪些量?利用这些来补充到dfs的调用参数中.
3.套用模板.
把需要的变量当做dfs的调用参数,以及决定dp数组是几维的;

目前我用过的变量:

HDU - 2089
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
Input
输入的都是整数对n、m(0

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

using namespace std;

int a, b, shu[20], dp[20][2];

int dfs(int len, bool if6, bool shangxian)
{
    if (len == 0)
        return 1;
    if (!shangxian && dp[len][if6])   //为什么要返回呢?可以画图理解当我们搜到3XXX时,程序运行到1XXX时就已经把3XXX之后的搜索完了,记忆化也是这个用意.
        return dp[len][if6];
    int cnt = 0, maxx = (shangxian ? shu[len] : 9);
    for (int i = 0; i <= maxx; i++)
    {
        if ((if6 && i == 2)||i==4)
            continue;
        cnt += dfs(len - 1, i == 6, shangxian && i == maxx);  //只有之前有限制现在的达到了上限才能构成限制
    }
    return shangxian ? cnt : dp[len][if6] = cnt; //如果有限制,那么就不能记忆化,否则记忆的是个错误的数.
}

int solve(int x)
{
    memset(shu, 0, sizeof(shu));
    int k = 0;
    while (x)
    {
        shu[++k] = x % 10;  //保存a,b的数
        x /= 10;
    }
    return dfs(k, false, true);
}

int main()
{
    while(~scanf("%d%d",&a,&b)&&(a||b))
    {
        printf("%d\n", solve(b) - solve(a - 1));

    }

}

除了注释外,还有一些我一开始不懂的点:
1. if (len == 0)
return 1;
表示如果一个数从前到后每一位遍历完,中间没有continue,说明这个数的数位不含不吉利的数;
2. if (!shangxian && dp[len][if6])

这里写图片描述

HDU - 3555 D - Bomb
The counter-terrorists found a time bomb in the dust. But this time the terrorists improve on the time bomb. The number sequence of the time bomb counts from 1 to N. If the current number sequence includes the sub-sequence “49”, the power of the blast would add one point.
Now the counter-terrorist knows the number N. They want to know the final points of the power. Can you help them?
Input
The first line of input consists of an integer T (1 <= T <= 10000), indicating the number of test cases. For each test case, there will be an integer N (1 <= N <= 2^63-1) as the description.

The input terminates by end of file marker.
Output
For each test case, output an integer indicating the final points of the power.
Sample Input
3
1
50
500
Sample Output
0
1
15

Hint
From 1 to 500, the numbers that include the sub-sequence “49” are “49”,”149”,”249”,”349”,”449”,”490”,”491”,”492”,”493”,”494”,”495”,”496”,”497”,”498”,”499”,
so the answer is 15.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long int ll;
ll a, shu[100], dp[100][2];
ll dfs(int len, bool if4, bool shangxian)
{
    if (len == 0)
        return 1;
    if (!shangxian && dp[len][if4])   //为什么要返回呢?可以画图理解当我们搜到3XXX时,程序运行到1XXX时就已经把3XXX之后的搜索完了,记忆化也是这个用意.
        return dp[len][if4];
    ll cnt = 0, maxx = (shangxian ? shu[len] : 9);
    for (int i = 0; i <= maxx; i++)
    {
        if (if4 && i == 9)
            continue;
        cnt += dfs(len - 1, i == 4, shangxian && i == maxx);  //只有之前有限制现在的达到了上限才能构成限制
    }
    return shangxian ? cnt : dp[len][if4] = cnt; //如果有限制,那么就不能记忆化,否则记忆的是个错误的数.
}

ll  solve(ll x)
{
    memset(shu, 0, sizeof(shu));
    int k = 0;
    while (x)
    {
        shu[++k] = x % 10;  //保存a,b的数
        x /= 10;
    }
    return dfs(k, false, true);
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&a);
        printf("%lld\n",a-solve(a)+1);

    }
}

E - Round Numbers POJ - 3252
The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone’ (also known as ‘Rock, Paper, Scissors’, ‘Ro, Sham, Bo’, and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can’t even flip a coin because it’s so hard to toss using hooves.

They have thus resorted to “round number” matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both “round numbers”, the first cow wins,
otherwise the second cow wins.

A positive integer N is said to be a “round number” if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.

Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many “round numbers” are in a given range.

Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).

Input
Line 1: Two space-separated integers, respectively Start and Finish.
Output
Line 1: A single integer that is the count of round numbers in the inclusive range Start.. Finish
Sample Input
2 12
Sample Output
6

题意:
一个区间内有多少个数的二进制形式中0的个数不少于1的个数;

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
ll shu[100],dp[100][100][100];
ll dfs(int len,int num0,int num1,bool shangxian,bool can)
{
    if(len==0)
    {
        if(num0>=num1)
            return 1;
        else
            return 0;
    }

    if(!shangxian&&dp[len][num0][num1])
    {
        return dp[len][num0][num1];
    }
    ll cnt=0,maxx=(shangxian?shu[len]:1);
    for(int i=0; i<=maxx; i++)
    {
        if(i==0)
        {
            if(can)
            {
                cnt+=dfs(len-1,num0+1,num1,(shangxian && i == maxx),can||i==1);
            }
            else
            {
                cnt+=dfs(len-1,num0,num1,(shangxian && i == maxx),can||i==1);
            }
        }
        if(i==1)
        {
            cnt+=dfs(len-1,num0,num1+1,(shangxian && i == maxx),can||i==1);

        }
    }
    return shangxian?cnt:dp[len][num0][num1]=cnt;
}
ll  solve(ll x)
{
    memset(shu, 0, sizeof(shu));
    int k = 0;
    while (x)
    {
        shu[++k] = x % 2;  //保存a,b的数
        x /= 2;
    }
    return dfs(k,0,0, true, false);
}
int main()
{
    ll a,b;
    while(~scanf("%lld%lld",&a,&b))
    {
        printf("%lld\n",solve(b)-solve(a-1));
    }

}

这里dfs的参数为num0,num1 0的个数和1的个数;
这里的数一开始拆成二进制而其他的拆成了十进制形式;
这个题还有一个坑就是00001这样的数是不存在的,所以要判断零的前面是否有1,也就是这个数是否存在;
dfs(len-1,num0,num1+1,(shangxian && i == maxx),can||i==1);

H - B-number HDU - 3652

A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string “13” and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task is to calculate how many wqb-numbers from 1 to n for a given integer n.
Input
Process till EOF. In each line, there is one positive integer n(1 <= n <= 1000000000).
Output
Print each answer in a single line.
Sample Input
13
100
200
1000
Sample Output
1
1
2
2

题意:判断一个数以内有多少个数是 包含13这个子串,且能整除13;

这个题先放一种做法,另一种正在改错;

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
ll shu[100],dp[100][100][100];
ll dfs(int len,int mod,int zhuangtai,bool shangxian)
{
    if(len==0)
    {
        if(mod==0&&zhuangtai==2)
            return 1;
        else
            return 0;
    }
    if(!shangxian&&dp[len][mod][zhuangtai])
    {
        return dp[len][mod][zhuangtai];
    }
    ll cnt=0,maxx=(shangxian?shu[len]:9);
    for(int i=0; i<=maxx; i++)
    {
        int tz=zhuangtai;
        if(zhuangtai!=2&&i!=1)
        {
            tz=0;
        }
        if(zhuangtai==1&&i==3)
        {
            tz=2;
        }
        if(i==1&&zhuangtai!=2)
        {
            tz=1;
        }
        cnt+=dfs(len-1,(mod*10+i)%13,tz,shangxian&&i==maxx);
    }
    return shangxian?cnt:dp[len][mod][zhuangtai]=cnt;
}
ll  solve(ll x)
{
    memset(shu, 0, sizeof(shu));
    int k = 0;
    while (x)
    {
        shu[++k] = x % 10;  //保存a,b的数
        x /= 10;
    }
    return dfs(k,0,0, true);
}
int main()
{
    ll a;
    while(~scanf("%lld",&a))
    {
        printf("%lld\n",solve(a));
    }
}

F - 吉哥系列故事――恨7不成妻 HDU - 4507
单身!
  依然单身!
  吉哥依然单身!
  DS级码农吉哥依然单身!
  所以,他生平最恨情人节,不管是214还是77,他都讨厌!
  
  吉哥观察了214和77这两个数,发现:
  2+1+4=7
  7+7=7*2
  77=7*11
  最终,他发现原来这一切归根到底都是因为和7有关!所以,他现在甚至讨厌一切和7有关的数!

  什么样的数和7有关呢?

  如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

  现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。
Input
输入数据的第一行是case数T(1 <= T <= 50),然后接下来的T行表示T个case;每个case在一行内包含两个正整数L, R(1 <= L <= R <= 10^18)。
Output
请计算[L,R]中和7无关的数字的平方和,并将结果对10^9 + 7 求模后输出。
Sample Input
3
1 9
10 11
17 17
Sample Output
236
221
0

1.这个题dfs的几个参数要记住;
2.平方和
这个题平方和的求解让我更理解递归;
网上一个大佬的分析
分析:很容易的看出来是数位DP,然后很容易的看出来他的dp要有三维,即位置pos,数字之和sum,以及整个数字对7的模数num。最麻烦的就是平方和的计算。·一开始自信满满的声明了longlong的变量到pos等于0的时候计算这个数的平方。wa了4发才浪子回头。。数字的范围是1e18的QAQ。那相乘肯定爆longlong了。。难道用大数?(自信的朋友可以去试试).然后参考了kuangbin的做法。如何求很多数的平方和呢?如下

一个十进制数可以写成X=ΣAi*Pi,(其中Ai为X每一位的值,Pi=10^i) 456 = 4*100+5*10+6*1;
因为(A+B)^2=A*A+2*A*B+B*B
(X1+X2+X3+…+Xn)^2=X1^2+2*X1*(X2+X3+…+Xn)+(X2+X3+…+Xn)^2
=X1^2+2*X1*(X2+X3+…+Xn)+X2^2+2*X2*(X3+…+Xn)+(X3+…+Xn)^2

……这样递推下去就可以把一个数转换为如456^2 = (4*100+5*10+6*1)^2 ;234^2 = (2*100+3*10+4*1)^2;那么456^2+234^2 = 400^2+56^2+2*400*56+200^2+34^2+2*200*34 = (400^2+200^2)+2*(400*56+200*34)+(56^2+34^2(这个括号里面两位数的还可以继续往下递推))。

上面的栗子展示了平方和结合在一起如何计算的方法:

首先我们要有当前位i…. ans1 = (i*pi)^2;如400,200,

其次我们要当前位之后的数字之和su。,56,34. ans2 =2*(su*i*pi);

最后,因为我们是要计算多个数的平方和,因此在当前状态下肯定不止一个数字可以到达。所以还需要存储一个cnt计数。最终答案是cnt*(ans1+ans2).

综上:在dp时,记录当前状态合法数的数量cnt,当前状态之后的和su,平方和po

A=Ai*Pi

now.cnt=now.cnt+nxt.cnt;

now.su=now.su+nxt.su+A*nxt.cnt;//因为su是计算和的,所以su实际上已经是cnt个数的累加了。
now.po=now.po+A*A*nxt.cnt+2*A*nxt.su+nxt.po;

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

using namespace std;
const int MOD = 1e9 + 7;
typedef long long LL;
int digit[20];
LL p[20];
struct My{
int cnt,su,po;
My(){}
My(int a,int b,int c):cnt(a),su(b),po(c){}
}dp[20][200][10];

My dfs(int pos,int sum,int num,int jud){
if(pos==0){
    My m;
    if(num==0||sum%7==0)m.cnt = 0;
    else m.cnt = 1;
    m.su = m.po = 0;
    return m;
}
if(!jud&&dp[pos][sum][num].cnt!=-1)return dp[pos][sum][num];
int sz = jud?digit[pos]:9;
My tmp,ans;
ans.cnt=ans.su=ans.po=0;
for(int i=0;i<=sz;i++)
{
    if(i==7)continue;
    tmp = dfs(pos-1,sum+i,(num*10+i)%7,jud&&(i==sz));
        ans.cnt+=tmp.cnt;
        ans.cnt%=MOD;
        ans.su+=(tmp.su+ ((p[pos-1]*i)%MOD)*tmp.cnt%MOD )%MOD;
        ans.su%=MOD;

        ans.po+=(tmp.po + ( (2*p[pos-1]*i)%MOD )*tmp.su)%MOD;
        ans.po%=MOD;
        ans.po+=( (tmp.cnt*p[pos-1])%MOD*p[pos-1]%MOD*i*i%MOD );
        ans.po%=MOD;
}
if(!jud)dp[pos][sum][num] = ans;
return ans;
}

My f(LL num)
{
    int pos = 0;
    while(num){
        digit[++pos] = num%10;
        num = num/10;
    }
    return dfs(pos,0,0,1);
}

int t;
LL l,r;
int main(){
    p[0] = 1;
    for(int i=1;i<20;i++)p[i] = (p[i-1]*10)%MOD;
   for(int i=0;i<20;i++)for(int j=0;j<200;j++)for(int k=0;k<10;k++)dp[i][j][k].cnt = -1;
    scanf("%d",&t);
    while(t--){
     scanf("%lld%lld",&l,&r);
    printf("%lld\n",(f(r).po-f(l-1).po+MOD)%MOD);
    }
return 0;
}

A - Beautiful numbers CodeForces - 55D
Volodya is an odd boy and his taste is strange as well. It seems to him that a positive integer number is beautiful if and only if it is divisible by each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.

Input
The first line of the input contains the number of cases t (1 ≤ t ≤ 10). Each of the next t lines contains two natural numbers li and ri (1 ≤ li ≤ ri ≤ 9 ·1018).

Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use cin (also you may use %I64d).

Output
Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from li to ri, inclusively).

Examples
Input
1
1 9
Output
9
Input
1
12 15
Output
2

题意:求美丽数的个数,美丽数是一个数能整除它的每一位;
思路:一个数能整除它的每一位数,那么就能整除它的每一位数的累积的最小公倍数;
代码:

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

using namespace std;

typedef  long long ll;
int index[2530];
int dis[30];
ll dp[30][2520][50];

void init()
{
    int num=0;
    for(int i=1; i<=2520; i++)
        if(2520%i==0)
            index[i]=num++;
}

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a%b);
}

int lcm(int a, int b)
{
    return a/gcd(a, b)*b;
}

ll dfs(int len,int sum,int lc ,int lim)
{
    if(len==0) return sum%lc==0?1:0;
    if(!lim&&dp[len][sum][index[lc]]) return dp[len][sum][index[lc]];
    ll res=0;
    int u=lim?dis[len]:9;
    for(int i=0; i<=u; i++)
    {
        int ssum=(sum*10+i)%2520;
        int llc=lc;
        if(i)
            llc=lcm(lc,i);
        res+=dfs(len-1,ssum,llc,lim&&i==u);
    }
    if(lim) return res;
    else return dp[len][sum][index[lc]]=res;
}

ll solve(ll x)
{
    int len=0;
    while(x)
    {
        dis[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,1,1);
}

ll a,b;

int main()
{
    int T;
    scanf("%d",&T);
    init();
    memset(dp,0,sizeof(dp));
    while(T--)
    {
        scanf("%I64d %I64d",&a,&b);
        printf("%I64d\n",solve(b)-solve(a-1));
    }
}

G - Balanced Number HDU - 3709
A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit. When a pivot is placed at some digit of the number, the distance from a digit to the pivot is the offset between it and the pivot. Then the torques of left part and right part can be calculated. It is balanced if they are the same. A balanced number must be balanced with the pivot at some of its digits. For example, 4139 is a balanced number with pivot fixed at 3. The torqueses are 4*2 + 1*1 = 9 and 9*1 = 9, for left part and right part, respectively. It’s your job
to calculate the number of balanced numbers in a given range [x, y].
Input
The input contains multiple test cases. The first line is the total number of cases T (0 < T ≤ 30). For each case, there are two integers separated by a space in a line, x and y. (0 ≤ x ≤ y ≤ 10 18).
Output
For each case, print the number of balanced numbers in the range [x, y] in a line.
Sample Input
2
0 9
7604 24324
Sample Output
10
897

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

using namespace std;

#define LL long long
LL dp[20][20][2000];
int dis[20];

LL dfs(int len, int pos, int sum, bool flag)
{
    if(len < 0)
        return sum?0:1;
    if(sum < 0)
        return 0;
    if(!flag && dp[len][pos][sum] != -1)
        return dp[len][pos][sum];
    LL ans = 0;
    int end = flag?dis[len]:9;
    for(int i=0; i<=end; i++)
    {
        ans += dfs(len-1, pos, sum+(len-pos)*i, flag&&i==end);
    }

    if(!flag)
        dp[len][pos][sum] = ans;
    return ans;
}

LL solve(LL n)
{
    int len = 0;
    while(n)
    {
        dis[len++] = n%10;
        n /= 10;
    }
    LL ans = 0;
    for(int i=0; i<len; i++)
        ans += dfs(len-1, i, 0, 1);
    return ans - (len-1);
}

int main()
{
    int T;
    scanf("%d", &T);
    memset(dp, -1, sizeof(dp));
    while(T--)
    {
        LL l, r;
        scanf("%lld%lld", &l, &r);
        printf("%lld\n", solve(r) - solve(l-1));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值