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 }
下面贴上我学习这份优秀代码的一点注释
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(这里我没想到)。
下来就是上面那个模板的套路去写。有几个需要注意的细节(其实都是我开始没想到的,对大佬来说这些很显然的东西,对我就从没显然过):
- 若扭矩和<0,直接返回,这里相当于利用一个剪枝,难思考但是不难理解。
- 就是在0的情况下,这种数无论pivot在哪都符合条件所以多级算了(len-1)次。dfs()搜索是0的情况是len位全0。
- 就是这道题有个记忆化搜索的如何记忆化的问题。什么时候更新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 }