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 }
View Code

然后是一开始自己想的版本(果然什么题都可以给我做繁掉):$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 }

 

posted @ 2019-04-08 16:54  Ametsuji_akiya  阅读(144)  评论(0编辑  收藏  举报