BZOJ4521 [Cqoi2016]手机号码[数位DP]
求给定区间内满足有三个相邻位数字相同且不同时出现8和4两个数字条件的数的个数。
比较简单的做法:记录前两位的数,和有没有之前高位有没有产生过3连位数字相同,以及之前8和4的出现情况,$f[len][if8][if4][pre1][pre2][exist]$。(个人辅助理解:之前一直不明白为什么记录前面位的状态,传统的dp不应该是低位向高位递推或者记搜么。现在看,就是说我不管之前填过的如何,我只看剩下的位有哪些方法,再考虑这些方法需要哪些限制来满足题目条件,也就是可能和之前填的高位有关,所以可以将之设计到状态之中,更为方便)然后瞎搜即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define iff if8||i==8,if4||i==4 7 #define lim limit&&i==num 8 using namespace std; 9 typedef long long ll; 10 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;} 11 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;} 12 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 13 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 14 template<typename T>inline T read(T&x){ 15 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 16 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 17 } 18 ll f[12][2][2][11][11][2],b[12]; 19 ll l,r; 20 ll dp(int len,bool if8,bool if4,int pre1,int pre2,bool exist,bool limit){//dbg(len,pre,k,"in"); 21 if(if8&&if4)return 0;if(!len)return exist; 22 if(!limit&&~f[len][if8][if4][pre1][pre2][exist])return f[len][if8][if4][pre1][pre2][exist]; 23 ll cnt=0;int num=limit?b[len]:9; 24 for(register int i=len==11;i<=num;++i)cnt+=dp(len-1,iff,i,pre1,exist||((pre1==pre2)&&(pre2==i)),lim); 25 return limit?cnt:f[len][if8][if4][pre1][pre2][exist]=cnt; 26 } 27 inline ll solve(ll x){ 28 if(x<1e10)return 0; 29 int k=0;while(x)b[++k]=x%10,x/=10; 30 return dp(k,0,0,10,10,0,1); 31 } 32 33 int main(){//freopen("tmp.txt","r",stdin);//freopen("test.out","w",stdout); 34 memset(f,-1,sizeof f); 35 read(l),read(r);return printf("%lld\n",solve(r)-solve(l-1)),0; 36 }
然后是一开始自己想的版本(果然什么题都可以给我做繁掉):$f[len][if8][if4][pre][k]$表示84记录,上一位填pre,现在填连续的k位相同数(k=3时表示至少3位)方案。然后根据当前递归层的k值讨论不同转移。k=3下一位连续填2个这一位数字,或者不填当前位,重新填3位。k=2,那么要求下一位填和自己当前填的相同的,k=1要求下一位不能填本位的数字。k=0小心,不是随便填的,不然会产生有连续3位相同的情况。。然后你就会开心的WA掉的说。k=0时要求我这一位不可和上位相同,下一位要么填现在本位的数要么填最多连续1个本位的数(不可填2个),不然会有重复。
WA记录:
- 宏定义错。最开头的东西,果然经常忽视。
- k=0的情况想当然。
- 注意line26括号。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define iff if8||i==8,if4||i==4//宏定义错。 7 #define lim limit&&i==num 8 #define dbg(x) cerr<<#x<<" = "<<x<<endl 9 using namespace std; 10 typedef long long ll; 11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;} 12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;} 13 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 14 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 15 template<typename T>inline T read(T&x){ 16 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 17 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 18 } 19 ll f[12][2][2][11][4],b[12]; 20 ll l,r; 21 ll dp(int len,bool if8,bool if4,int pre,int k,bool limit){ 22 if(if8&&if4||len<k)return 0;if(!len&&!k)return 1; 23 if(!limit&&~f[len][if8][if4][pre][k])return f[len][if8][if4][pre][k]; 24 ll cnt=0;int num=limit?b[len]:9; 25 if(k==3)for(register int i=len==11;i<=num;++i)cnt+=dp(len-1,iff,i,2,lim)+dp(len-1,iff,0,3,lim); 26 else if(!k){for(register int i=0;i<=num;++i)if(i^pre)cnt+=dp(len-1,iff,i,0,lim)+dp(len-1,iff,i,1,lim);}//'{}'must be added.Please think about the transfer equation twice! 27 else cnt=pre<=num?dp(len-1,pre==8||if8,pre==4||if4,pre,k-1,limit&&pre==num):0;//k is 1 or 2. 28 return limit?cnt:f[len][if8][if4][pre][k]=cnt; 29 } 30 inline ll solve(ll x){ 31 if(x<1e10)return 0;memset(f,-1,sizeof f); 32 int k=0;while(x)b[++k]=x%10,x/=10; 33 return dp(k,0,0,0,3,1); 34 } 35 36 int main(){//freopen("tmp.txt","r",stdin);//freopen("test.out","w",stdout); 37 read(l),read(r);return printf("%lld\n",solve(r)-solve(l-1)),0; 38 }