【[CQOI2016]手機號碼】
阿新 • • 發佈:2019-01-01
遞推版的數位dp
絕對的暴力美學
我們設\(dp[l][i][j][0/1][0/1][0/1]\)表示到了第\(l\)位,這一位上選擇的數是\(i\),\(l-1\)位選擇的數是\(j\),第一個\(0/1\)代表\(4\)沒有/有出現過,第二個\(0/1\)代表\(8\)沒有/有出現過,第三個\(0/1\)代表連續三位沒有/有出現過
於是轉移很簡單了
但是卡位實在是鬼畜
我卡位的方式有些鬼畜,所以細節非常的多
#include<iostream> #include<cstring> #include<cstdio> #define re register #define maxn 16 #define LL long long LL L,R; int a[maxn],num; LL dp[maxn][11][11][2][2][2]; //位數,這一位上的數,上一位的數,0/1表示4/8有/沒有出現,0/1表示有/沒有連續三位 inline LL slove(LL x) { memset(dp,0,sizeof(dp)); memset(a,0,sizeof(a)); num=0; while(x) { a[++num]=x%10; x/=10; }//分解數位 a[num+1]=-11,a[num+2]=11; for(re int i=0;i<=9;i++) for(re int j=0;j<=9;j++) for(re int k=0;k<=9;k++) { int opt_4=0,opt_8=0; if(i==4||j==4||k==4) opt_4=1; if(i==8||j==8||k==8) opt_8=1; if(i==j&&j==k) dp[3][i][j][opt_4][opt_8][1]+=1; else dp[3][i][j][opt_4][opt_8][0]+=1; }//先初始化dp[3]之後再往下推 for(re int l=3;l<num;l++)//刷錶轉移 for(re int i=0;i<=9;i++) for(re int j=0;j<=9;j++) for(re int k=0;k<=9;k++) for(re int o4=0;o4<=1;o4++) for(re int o8=0;o8<=1;o8++) for(re int o=0;o<=1;o++) dp[l+1][i][j][(i==4)||o4][(i==8)||o8][((i==j)&&(j==k))||o]+=dp[l][j][k][o4][o8][o]; //方程其實挺簡單的,就是看看這一位有沒有4/8/連續三位就好了 LL ans=0; for(re int i=3;i<num;i++)//從位數小於給定數的開始 for(re int j=1;j<=9;j++) for(re int k=0;k<=9;k++) ans+=dp[i][j][k][0][0][1]+dp[i][j][k][1][0][1]+dp[i][j][k][0][1][1]; for(re int i=1;i<a[num];i++)//位數和給定數相等,但是首位比較小 for(re int j=0;j<=9;j++) ans+=dp[num][i][j][0][0][1]+dp[num][i][j][1][0][1]+dp[num][i][j][0][1][1]; int o4=0,o8=0,o=0;// 4/8/連續三位有沒有出現過 if(a[num]==4) o4=1; if(a[num]==8) o8=1; for(re int l=num-1;l>=3;l--)//卡位,這裡保證從[l+1,num]和給定數是完全相等的 { if(o4&&o8) break; for(re int i=0;i<a[l];i++) { int flag=o; if(i==a[l+1]&&a[l+1]==a[l+2]) o=1;//由於我們選擇這一位有可能會導致和上面的兩位重複,於是這裡需要判斷一下,如果有那麼就o=1接下來就算沒有選出連續三位也可以了 for(re int j=0;j<=9;j++) { int cnt=o; if(i==j&&i==a[l+1]) o=1; //和上面兩位重合的情況 if(o) { if(o4&&!o8) ans+=dp[l][i][j][1][0][1]+dp[l][i][j][0][0][1] +dp[l][i][j][0][0][0]+dp[l][i][j][1][0][0]; if(!o4) { if(!o8) ans+=dp[l][i][j][0][0][1]+dp[l][i][j][0][0][0] +dp[l][i][j][1][0][1]+dp[l][i][j][1][0][0] +dp[l][i][j][0][1][1]+dp[l][i][j][0][1][0]; if(o8) ans+=dp[l][i][j][0][1][1]+dp[l][i][j][0][1][0] +dp[l][i][j][0][0][1]+dp[l][i][j][0][0][0]; } } if(!o) { if(o4&&!o8) ans+=dp[l][i][j][1][0][1]+dp[l][i][j][0][0][1]; if(!o4) { if(!o8) ans+=dp[l][i][j][0][0][1]+dp[l][i][j][1][0][1]+dp[l][i][j][0][1][1]; if(o8) ans+=dp[l][i][j][0][1][1]+dp[l][i][j][0][0][1]; } } o=cnt; } o=flag;//將o還原回來 } o4=o4||(a[l]==4); o8=o8||(a[l]==8); o=o||((a[l]==a[l+1])&&(a[l+1]==a[l+2])); //判斷有沒有連續三位4/8出現過 } if(o4&&o8) return ans; for(re int i=0;i<=9;i++) for(re int j=0;j<=9;j++)//卡最後兩位 { if(i*10+j>a[2]*10+a[1]) continue;//不能超過給定數的最後兩位 if(o4&&o8) continue; if(i==4||j==4) if(o8) continue;//有4就不能有8 if(i==8||j==8) if(o4) continue;//有8就不能有4 if(i==4&&j==8) continue; if(i==8&&j==4) continue;//更不可能同時出現 if(o||(i==j&&j==a[3])||(i==a[3]&&a[3]==a[4])) ans++; //最後兩位仍有可能和前面構成三位連續 } return ans; } int main() { scanf("%lld%lld",&L,&R); printf("%lld\n",slove(R)-slove(L-1)); return 0; }