数位dp作业

dp东西实在太多,昨天开了个树形dp入门,还没入呢,今天就要写数位dp,也不知道这种学习状态对不对啊?

A - 不要62

题意:

输入n到m内,符合条件的数的个数。条件:不含62和4。

这里直接学习qkoqhh大佬的思路。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<iostream>
 5 #include<queue>
 6 #include<cmath>
 7 #define inc(i,l,r) for(int i=l;i<=r;i++)
 8 #define dec(i,l,r) for(int i=l;i>=r;i--)
 9 #define link(x) for(edge *j=h[x];j;j=j->next)
10 #define eps 1e-8
11 #define inf 1000000007
12 #define mem(a) memset(a,0,sizeof(a))
13 #define ll long long
14 #define succ(x) (1<<x)
15 #define lowbit(x) (x&(-x))
16 #define sqr(x) ((x)*(x))
17 #define mid (x+y>>1)
18 #define NM 305
19 #define nm 1000005
20 #define pi 3.141592653
21 using namespace std;
22 int read(){
23     int x=0,f=1;char ch=getchar();
24     while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
25     while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
26     return f*x;
27 }
28 int d[10][10],_x,n,b[10];
29 //f:高位与原位是否相同 
30 int dfs(int n,bool f,int t){
31     if(!n)return 1;
32     if(!f&&d[n][t])return d[n][t];
33     int ans=0,m=f?b[n]:9;
34     for(int i=0;i<=m;i++)
35         if(i!=4&&!(t==6&&i==2)){
36             ans+=dfs(n-1,f&&i==m,i);
37             //printf("%d %d:%d
",n,i,ans);
38         }
39     return d[n][t]=ans;
40 }
41 int solve(int x){
42     mem(b);mem(d);
43     for(n=0;x;x/=10)b[++n]=x%10;
44     return dfs(n,true,0);
45 }
46 int main(){
47     while(_x=read())printf("%d
",solve(read())-solve(_x-1));
48     return 0;
49 }
View Code

下面贴上我学习这份优秀代码的一点注释

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 int bit[25], d[25][25];
 6 int dfs(int pos, bool flag, int t){
 7 /*
 8  * pos:当前位的位置;
 9  * flag:高位与原位相同(true)
10  * t:高位,即当前位的前一位 
11 */
12     if(!pos)    return 1;
13     if(!flag&&d[pos][t])    return d[pos][t];
14     //高位与原位不同, 直接返回,不用考虑当前位与原位大小
15     int ans = 0, m = flag?bit[pos]:9;
16     for(int i=0;i<=m;i++){
17         if(i!=4 && !(i==2&&t==6)){
18             ans+=dfs(pos-1, flag&&i==m, i);
19             //这里第二个参数可依据flag的定义理解
20             //同样这里比较灵活,有时需要分类讨论 
21         }
22     }
23     return d[pos][t]=ans;
24 }
25 int solve(int x){
26     memset(bit, 0, sizeof(bit));
27     memset(d, 0, sizeof(d));
28     int len = 0;
29     while(x){
30         bit[++len] = x%10;
31         x /= 10;
32     }
33     return dfs(len, true, 0);
34 }
35 int main(){
36     int n, m;
37     while( scanf("%d%d", &n, &m)!=EOF && n && m){
38         printf("%d
", solve(m)-solve(n-1));
39     }
40     return 0;
41 }

B - B-number

题意: 统计1到n中有13且能被13整除的数。

这是我写的第二道数位dp题,目前对我来说,数位dp题的难点在于状态的转移合理正确的统计(这个比较灵活)。

拿到这道题,思考后对比上题的模板,需要新增参数判断高位前是否出现了13,以及如何处理能被13整除。这里余数的处理用到了 同余定理乘法以及加法的运用,这个技巧我不熟练(不知道),这里余数的参数传递就有难度了。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 int bit[25], d[25][25][13][2], c[25];
 6 bool vis[25][25][13][2];
 7 int dfs(int pos, bool flag, bool is13, int t, int mod){
 8 /*
 9  * pos:当前位的位置;
10  * flag:高位与原位相同(true)
11  * is13:高位前是否出现13 
12  * t:高位,即当前位的前一位 
13  * mod:当前数/13的余数 
14 */
15     if(!pos){
16         if(is13 && !mod) return 1;  //出现13,且mod为0,可以整除
17         else    return 0; 
18     }
19     if(!flag&&vis[pos][t][mod][is13])    return d[pos][t][mod][is13];
20     int ans = 0, m = flag?bit[pos]:9;
21     for(int i=0;i<=m;i++){
22         if((t==1 && i==3) || is13){
23             /*
24              *int chushu = (mod*10 + i)%13;
25              *这里不能这样算,有种自己在胡搞的感觉 
26              *
27              * 这里是同余定理加法和乘法的应用,其实不用深究,写个例子就明白了 
28              * 可以这样理解 :
29              * x%13=(c[i]*10^i + c[j]*10^j + ……)%13 = (mod + c[j]*10^j + ……)%13 
30              * 拆开后就好
31              * 这里可以预处理c[]
32              * 传递时仔细对待mod
33             */
34             int chushu = (mod + c[pos]*i)%13;
35             ans += dfs(pos-1, flag&&i==m, true, i, chushu);
36         }else{
37             int chushu = (mod + c[pos]*i)%13;
38             ans += dfs(pos-1, flag&&i==m, false, i, chushu);
39         }    
40     }
41     vis[pos][t][mod][is13]=true;
42     return d[pos][t][mod][is13]=ans;
43 }
44 int solve(int x){
45     memset(bit, 0, sizeof(bit));
46     memset(d, 0, sizeof(d));
47     memset(vis, 0, sizeof(vis)); 
48     int len = 0;
49     while(x){
50         bit[++len] = x%10;
51         x /= 10;
52     }
53     return dfs(len, true, false, 0, 0);
54 }
55 int main(){
56     int n;
57     c[1] = 1;
58     for(int i=2; i<=10; i++) c[i]=c[i-1]*10%13;
59     while( scanf("%d", &n)!=EOF && n ){
60         printf("%d
", solve(n));
61     }
62     return 0;
63 }

C - Balance Number

题意:

定义一种平衡数:在pivot两边的数与pivot的torques之和相等,统计[x,y]内bn的个数。

这里记录下做这题的思路(虽然这题写出来没过样例,但感觉自己思路还说的过去):

先思考状态转移所需要的参数,即维度:除前面模板里套路的pos和flag外,还需要pivot的位置,还需要计算和比较pivot两边的torques之和是否相等。

前三个维度各设一个参数,最后的比较可以设置为一个torqueses,对左右扭矩求向量和,若符合条件,则最终和为0(这里我没想到)。

下来就是上面那个模板的套路去写。有几个需要注意的细节(其实都是我开始没想到的,对大佬来说这些很显然的东西,对我就从没显然过):

  1. 若扭矩和<0,直接返回,这里相当于利用一个剪枝,难思考但是不难理解。
  2. 就是在0的情况下,这种数无论pivot在哪都符合条件所以多级算了(len-1)次。dfs()搜索是0的情况是len位全0。
  3. 就是这道题有个记忆化搜索的如何记忆化的问题。什么时候更新dp,时间上还是有差距的,下面的代码在这点上与上面的模板不同。
 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 #define LL long long
 6 const int INF = 0x3f3f3f3f;
 7 LL dp[20][20][2500];
 8 int bit[20];
 9 /*
10  * pos:当前位位置
11  * pivot: 题中所描述的balanced number的"中间"的那个点
12  * torques:这里判断pivot左右扭矩之和是否相等可以用求其和torques看是否等于0
13  *          这里最开始我的想法是用两个参数,扫描到pos=0时判断其是否相等,这样就烦琐了 
14  * flag:同理前面的模板 
15 */
16 LL dfs(int pos, int pivot, int torques, bool flag){
17     if(!pos)    return torques==0;
18     //若枚举结束,扭矩和为0,则说明pivot这点可以使这个数成为balance number
19     if(torques < 0)    return 0;
20     //这里如果没有搜索完,扭矩和就小于0,则这个数一定不会是b n,这个判断很巧妙,这个有了方向后计算很方便 
21     if(!flag && dp[pos][pivot][torques]!=-1) return dp[pos][pivot][torques];    
22     LL ans = 0;
23     LL m = flag?bit[pos]:9;
24     for(int i=0; i<=m; i++){
25         ans += dfs(pos-1, pivot, torques+i*(pos-pivot), flag&&i==m);
26     }
27     if(!flag)  dp[pos][pivot][torques]=ans;
28     /*
29      *  1、这里相对前面的模板不同。 这里记忆化对我来说又得注释一下, 
30      *     这里高位与低位相同时, 
31      *     当前pos是不能记忆的因为这里只枚举了0-bit[i]位,不符合总的dp[][][]状态的定义
32      *     pos位,pivot点,扭矩和为torques的所有数 的个数
33      *  2、同样也可以按前面的模板来写,不过需要对每个数的dp进行memset()。
34      *  
35      * 对比下二者的时间,156 : 249 
36     */ 
37     return ans;
38 }
39 LL solve(LL x){
40     if(x < 0)    return 0;
41     memset(bit, 0, sizeof(bit));
42     int len = 0;
43     while(x){
44         bit[++len] = x%10;
45         x /= 10;
46     }
47     LL ans = 0;
48     for(int i=1; i<=len; i++){
49         ans += dfs(len, i, 0, true);
50     }
51     return ans-(len-1);
52     /*
53      * 这里结果减去(len-1)我又没想到,在纸上画了画,
54      * 发现在dfs过程中,重复计算了一种为0的情况,
55      * 这个情况要筛去(len-1)个 
56     */ 
57 }
58 int main(){
59     int T;
60     scanf("%d", &T);
61     memset(dp, -1, sizeof(dp));
62     while(T--){
63         LL x, y;
64         scanf("%lld%lld", &x, &y);
65         printf("%lld
", solve(y)-solve(x-1));
66     }
67 }

还有道C,改天补。

这里想到了dp第一天韦神对A题有一种不同于上面那种解法的解法,因为代码看着比较复杂,一直没有仔细看,只是粗略的对比了二份代码的不同,发现那个思路比较注重对各种情况的全面讨论,难点在于对状态的抽象以及转移过程中的分类讨论,但是dp的维度是优于这个的,情况的表示也相对直观。这里先放上代码,改日细看。

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <string.h>
 4 #include <iostream>
 5 using namespace std;
 6 int dp[10][3];//dp[i][j],i代表数字的位数,j代表状况
 7 //dp[i][0],表示不存在不吉利数字
 8 //dp[i][1],表示不存在不吉利数字,且最高位为2
 9 //dp[i][2],表示存在不吉利数字
10 void Init()
11 {
12     memset(dp,0,sizeof(dp));
13     int i;
14     dp[0][0] = 1;
15     for(i = 1; i<=6; i++)//数字最长为6
16     {
17         dp[i][0] = dp[i-1][0]*9-dp[i-1][1];//最高位加上不含4的9个数字的状况,但因为会放6,所以要减去前一种开头为2的情况
18         dp[i][1] = dp[i-1][0];//开头只放了2
19         dp[i][2] = dp[i-1][2]*10+dp[i-1][0]+dp[i-1][1];//已经含有的前面放什么数都可以,或者是放一个4,或者是在2前面放6
20     }
21 }
22 
23 int solve(int n)
24 {
25     int i,len = 0,tem = n,ans,flag,a[10];
26     while(n)//将每一位拆分放入数组
27     {
28         a[++len] = n%10;
29         n/=10;
30     }
31     a[len+1] = ans = 0;
32     flag = 0;
33     int bb=0;
34     for(i=len; i>=1; i--)
35     {
36         ans+=dp[i-1][2]*a[i];
37         if(!flag && a[i]>4)//首位大于4,可以有放4的情况
38             ans+=dp[i-1][0];
39         if(!flag && a[i+1]==6 && a[i]>2)//后一位为6,此位大于2
40             ans+=dp[i][1];
41         if(!flag && a[i]>6)//此位大于6,可能的62状况
42             ans+=dp[i-1][1];
43         if(a[i]==4 || (a[i+1]==6&&a[i]==2)){
44             bb=0;
45             for(int j=i-1;j>=1;j--){
46                 bb=bb*10+a[j];
47             }
48             break;
49         }
50     }
51    return tem-ans-bb;
52 }
53 
54 int main()
55 {
56     int l,r;
57     Init();
58     while(~scanf("%d%d",&l,&r),l+r)
59     {
60     // solve(4555);
61         printf("%d
",solve(r+1)-solve(l));
62         //因为solve函数中并没有考虑n是不是不幸数的情况,所以r+1只算了1~r,而l只算了1~l-1,这两者相减才是正确答案
63    }
64 
65     return 0;
66 }
View Code
原文地址:https://www.cnblogs.com/seaupnice/p/9479082.html