1. 程式人生 > >LibreOJ2044 - 「CQOI2016」手機號碼

LibreOJ2044 - 「CQOI2016」手機號碼

超過 AR herf des truct ret 問題 esc lint

Portal

Description

給出兩個十一位數\(L,R\),求\([L,R]\)內所有滿足以下兩個條件的數的個數。

  • 出現至少\(3\)個相鄰的相同數字;
  • 不能同時出現\(4\)\(8\)

Solution

數位DP。
首先將問題轉換成\(solve(R)-solve(L)\)的形式,這樣只需要求不超過\(n\)的滿足條件的數的個數。
定義\(dp[k][x][f_1][f_2][f_3][f_4]\),其中\(k\)表示位數,\(x\)表示尾數,\(f_1\)表示第\(k\)位與第\(k-1\)位是否相同,\(f_2\)表示是否出現過三連,\(f_3\)表示\(4,8\)的出現情況(\(00,01,10,11\)

),\(f_4\)表示是否在第\(k\)達到上限。
考慮第\(k+1\)位的每種取值\(i\)。若\(i=x\),則\(f_1=1\);若已有三連或原\(f_1=1\)\(i=x\),則\(f_2=1\);若\(i\)等於\(4\)\(8\),改變\(f_3\);若在第\(k\)位就達到上限且\(i\)等於n的第\(k+1\)位,則\(f_4=1\)
用隊列進行轉移或循環每一維即可解決。

時間復雜度\(O(11×10×2×2×4×2\cdot10)\)

Code

//「CQOI2016」手機號碼
#include <cstdio>
#include <cstring>
#include <queue> typedef long long lint; struct state { int k,x,f1,f2,f3,f4; state(int _k,int _x,int _f1,int _f2,int _f3,int _f4) {k=_k,x=_x,f1=_f1,f2=_f2,f3=_f3,f4=_f4;} }; const int LEN=11; lint dp[12][10][2][2][4][2]; std::queue<state> Q; int is48(int x) {return (x==8)<<1
|(x==4);} lint solve(lint n) { memset(dp,0,sizeof dp); int v[12]; for(lint i=LEN,t=n;i>=1;i--,t/=10) v[i]=t%10; for(int i=1;i<=v[1];i++) { int f3=is48(i),f4=(i==v[1]); dp[1][i][0][0][f3][f4]=1; Q.push(state(1,i,0,0,f3,f4)); } while(!Q.empty()) { state s=Q.front(); Q.pop(); int k=s.k,x=s.x,f1=s.f1,f2=s.f2,f3=s.f3,f4=s.f4,val=dp[k][x][f1][f2][f3][f4]; if(k==LEN) continue; int t=f4?v[k+1]:9; for(int i=0;i<=t;i++) { int _f1=(i==x),_f2=f2||f1&&(i==x),_f3=f3|is48(i),_f4=f4&&i==t; lint &r=dp[k+1][i][_f1][_f2][_f3][_f4]; if(!r) Q.push(state(k+1,i,_f1,_f2,_f3,_f4)); r+=val; } } lint r=0; for(int i=0;i<=9;i++) for(int _f1=0;_f1<=1;_f1++) for(int _f3=0;_f3<=2;_f3++) r+=dp[LEN][i][_f1][1][_f3][0]+dp[LEN][i][_f1][1][_f3][1]; return r; } int main() { lint L,R; scanf("%lld%lld",&L,&R); if(L==(lint)1e10) printf("%lld\n",solve(R)-solve(L)+1); else printf("%lld\n",solve(R)-solve(L-1)); return 0; }

LibreOJ2044 - 「CQOI2016」手機號碼