codeforces 55d (lcm+數位dp)附板子
阿新 • • 發佈:2019-02-17
就是學習的人家的,很久不寫,數位dp也忘記了咋寫。
程式碼如下:
注意dp只需要初始化時更新一次就好,
dp[i][j][k] 表示,右數第 i 位 ,mod為 j 時,lcm為k 的符合要求數目, 此處把lcm離散化了一下,
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <string> #include <map> #include <vector> using namespace std; const int MOD = 2520; long long dp[30][2525][50]; int num[20]; int has[3000]; int s[3000]; int gcd(int a,int b) { if(!b) return a; else return gcd(b,a%b); } int getlcm(int a,int b) { int d = gcd(a,b); return (a/d)*b; } long long dfs(int pos,int mod,int lcm,int limit) { if(pos<0) return mod%lcm==0; if(!limit && dp[pos][mod][has[lcm]]!=-1) return dp[pos][mod][has[lcm]]; int tlcm; int endi = 9; if(limit) endi = num[pos]; long long res = 0; for(int i=0; i<=endi; i++) { if(!i) tlcm = lcm; else tlcm = getlcm(i,lcm); res +=dfs(pos-1,(mod*10+i)%MOD,tlcm,limit&&(i==endi)); } if(!limit) dp[pos][mod][has[lcm]] = res; return res; } long long solve(long long n) { int cnt = 0; while(n>0) { num[cnt++] = n%10; n/=10; } return dfs(cnt-1,0,1,1); } void init() { memset(has,0,sizeof(has)); int l = 0; for(int i=0; i<(1<<9);i++) { int now = 1; for(int j=1;j<9;j++) { if(i&(1<<j)) { now = getlcm(now,j+1); } } has[now] = 1; } for(int i=0; i<=MOD; i++) { if(has[i]) { s[l]= i; has[i] = l++; } } } int main() { memset(dp,-1,sizeof(dp)); int t; cin>>t; init(); while(t--) { long long l,r; cin>>l>>r; long long ans1 = solve(l-1); long long ans2 = solve(r); cout<<ans2-ans1<<endl; // cout<<getlcm(l,r)<<endl; } return 0; }
再接一個hls的數位dp板子
typedef long long ll; int a[20]; ll dp[20][state];//不同題目狀態不同 ll dfs(int pos,/*state變數*/,bool lead/*前導零*/,bool limit/*數位上界變數*/)//不是每個題都要判斷前導零 { //遞迴邊界,既然是按位列舉,最低位是0,那麼pos==-1說明這個數我列舉完了 if(pos==-1) return 1; /*這裡一般返回1,表示你列舉的這個數是合法的,那麼這裡就需要你在列舉時必須每一位都要滿足題目條件, 也就是說當前列舉到pos位,一定要保證前面已經列舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */ //第二個就是記憶化(在此前可能不同題目還能有一些剪枝) if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state]; /*常規寫法都是在沒有限制的條件記憶化,這裡與下面記錄狀態是對應,具體為什麼是有條件的記憶化後面會講*/ int up=limit?a[pos]:9;//根據limit判斷列舉的上界up;這個的例子前面用213講過了 ll ans=0; //開始計數 for(int i=0;i<=up;i++)//列舉,然後把不同情況的個數加到ans就可以了 { if() ... else if()... ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變數傳參都是這樣寫的 /*這裡還算比較靈活,不過做幾個題就覺得這裡也是套路了 大概就是說,我當前數位列舉的數是i,然後根據題目的約束條件分類討論 去計算不同情況下的個數,還有要根據state變數來保證i的合法性,比如題目 要求數位上不能有62連續出現,那麼就是state就是要儲存前一位pre,然後分類, 前一位如果是6那麼這意味就不能是2,這裡一定要儲存列舉的這個數是合法*/ } //計算完,記錄狀態 if(!limit && !lead) dp[pos][state]=ans; /*這裡對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裡就是lead就完全不用考慮了*/ return ans; } ll solve(ll x) { int pos=0; while(x)//把數位都分解出來 { a[pos++]=x%10;//個人老是喜歡編號為[0,pos),看不慣的就按自己習慣來,反正注意數位邊界就行 x/=10; } return dfs(pos-1/*從最高位開始列舉*/,/*一系列狀態 */,true,true); //剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛 } int main() { ll le,ri; while(~scanf("%lld%lld",&le,&ri)) { //初始化dp陣列為-1,這裡還有更加優美的優化,後面講 printf("%lld\n",solve(ri)-solve(le-1)); } }