數位dp 的簡單入門

時間緊張,就不講那麼詳細了。html

以前一直被深搜代碼誤解,覺得數位dp 其實就是記憶化深搜...(雖然說爆搜確實很舒服並且還好想)ios

可是後來發現數位dp 的標準格式實際上是 預處理 + dp ......c++

 

數位dp 的介紹

數位 dp 其實就是讓你處理出某一區間範圍內知足條件的數的個數,可是通常這個區間範圍都是使人絕望的大...好比 1e9 都算良心了,常規的都是 1e18 甚至是 1e10n (n 通常爲 3 或 5)次這樣的...git

 

數位dp 的通常解法

那麼咱們知道確定不能在區間內一個個去判斷數字是否知足條件,因而咱們就引入了 數位處理 的概念:數組

處理很少於 i 位的數中 (如 i = 十、100、1000、1e四、1e5)有多少數知足條件,dp[i] 就表示小於等於 10的數中,有多少數知足條件。ide

但大部分題目與數字自己有必定關係(如不能出現某些特定數字或者必須出現某些式子之類的),那麼咱們就要多加一維(或者多維)來進行轉移,函數

那麼 dp[i][j] 就表示第 i 位爲 j 的,知足條件的數字有多少個。工具

接下來咱們再按位對於 區間端點 進行處理。這裏爲何是對區間端點處理?(不是顯然麼?否則你枚舉區間內每一個數?)網站

回想以前的操做,咱們能夠用預處理出的信息來計算出 1 ~ 某個數  的範圍內有多少數知足條件,又由於咱們計算出的是前綴信息,知足區間可加(減)性,ui

因而咱們能夠計算出 $1 到 L-1$ 和 $1 到 R$ ( [L,R] 爲要計算的知足條件的數字所處的區間範圍 )這兩個區間內有多少數知足條件,而後減一減答案就出來了。

具體怎麼實現?咱們通常考慮在當前處理的位 (i) 上固定一個數字(但這個數要小於 n 的第 i 位),而後易知,後面的數字咱們隨便填都行,

弄完後咱們把 n 的第 i 位當作填上去了(或者是記錄下來和下一位做比較,這要看題目而定),迭代處理接下來的步驟。 

然而數位dp 說是說 dp ,有的時候 dp 數組是預處理出來累加答案的...優秀!

 

相信這些東西看完也沒多大用處(畢竟實踐出真知嘛,你們都是作題作着作着才明白理論都在講啥的麼...)

 

數位dp 基礎入門

先來三道藍題(真的沒有顏色更淺的題了啊QwQ)

  1. 洛谷 P2657 [SCOI2009]windy數
  2. 洛谷 P2518 [HAOI2010]計數
  3. 洛谷 P3413 SAC#1 - 萌數

相信大家都看到了題目前的標籤...(瓦特?省選?FAQ!)  。。。可是騷年不要棄療啊...你作着作着就發現沒這麼恐怖啦!

 

1.洛谷 P2657 [SCOI2009]windy數

題目描述

windy定義了一種windy數。不含前導零且相鄰兩個數字之差至少爲2的正整數被稱爲windy數。 windy想知道,

在A和B之間,包括A和B,總共有多少個windy數?

輸入輸出格式

輸入格式:

包含兩個整數,A B。

 

輸出格式:

一個整數

 

分析

不難,預處理一下 i 位數的範圍內多少數字知足相差大於 1 就行了,而後常規 solv 也很難解釋清楚,具體得看代碼。

 

代碼

 1 //by Judge
 2 #include<iostream>
 3 #include<cstdio>
 4 #define ll long long
 5 using namespace std;
 6 const int M=21;
 7 ll f[M][10][10],d[M],l,r,ans;
 8 inline bool get(int x){ return x>1 || x<-1; } //判斷是否知足條件
 9 inline void prep(){ //預處理,不難看懂?
10     for(int i=2,x,y,z;i<=10;++i) for(x=0;x<=9;++x)
11         for(y=0;y<=9;++y) if(get(x-y)){ //若是知足條件就進行枚舉
12             for(z=0;z<=9;++z) if(get(y-z)) //若是知足條件就累加
13                 f[i][x][y]+=f[i-1][y][z];
14             if(i==2) ++f[i][x][y]; //特殊狀況,i==2 沒法累加,直接手動+1
15         }
16 }
17 inline ll solv(int n,ll res=0,int len=0){
18     if(n<10) return n; //10之內的數必然都知足,直接返回,不然進行數的分解
19     while(n) d[++len]=n%10,n/=10; int las=-1; //las是上一位數,具體看下面才清楚
20     for(int i=2,j,k;i<len;++i) for(j=1;j<=9;++j) //枚舉狀態(因爲位數小於當前 n 的數是不受限制的,能夠直接累加),至於位數等於 len 的咱們另外處理
21         for(k=0;k<=9;++k) res+=f[i][j][k];
22     res+=9; bool flag=1; //答案+9(其實就是 1~9 這 9 個數字),而後立一個 flag
23     for(int i=len,j,k;i>1;--i){ //自高向低位處理
24         for(j=0;j<d[i];++j) if((i^len || j) && get(j-las)) //最高位不爲零,且 j 和上一位數字相差>1 則開始枚舉
25             for(k=0;k<=9;++k) res+=f[i][j][k]; //知足條件則累加答案
26         if(!get(las-d[i])){ flag=0; break; } las=d[i]; //若是處理到當前位已經不知足條件了  不然 las 記錄當前數,爲下一次計算準備
27     }
28     if(flag) for(int j=0;j<=d[1];++j) //若是 flag 還在,說明前面的 2~len 的數都知足條件,那麼最低位累加答案
29         if(get(j-las)) ++res;
30     return res;
31 }
32 signed main(){
33     cin>>l>>r,prep(); //預處理
34     ans=solv(r)-solv(l-1); //計算答案
35     printf("%lld\n",ans);
36     return 0;
37 }

 

 

相信有了第一道題的入門,後面就沒有那麼麻煩了...因而乎~後面我代碼裏面的註釋就不那麼囉嗦了(反正你這麼聰明必定看得懂的)

 

 

2.洛谷 P2518 [HAOI2010]計數

題目描述

你有一組非零數字(不必定惟一),你能夠在其中插入任意個0,這樣就能夠產生無限個數。好比說給定{1,2},那麼能夠生成數字12,21,102,120,201,210,1002,1020,等等。

如今給定一個數,問在這個數以前有多少個數。(注意這個數不會有前導0).

輸入輸出格式

輸入格式:

 

只有1行,爲1個整數n.

 

輸出格式: 

只有整數,表示N以前出現的數的個數。

 

分析

這道題其實沒這麼難!預處理組合數就行!

然而想一想本身作的時候傻* 了... 在放置數字的時候直接排列(也就是用階乘 乘上去),完了還奇怪怎麼答案這麼大

注意!相同的數字調換了位置仍是算一種方案!(對本身說的吧...)

 

代碼

 1 //by Judge
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 #define ll long long
 6 using namespace std;
 7 char s[60];
 8 ll len,d[60],num[15],C[60][60];
 9 inline void prep(){ //預處理組合數
10     for(int i=0;i<=50;++i) C[i][0]=1;
11     for(int i=1,j;i<=50;++i) for(j=1;j<=50;++j)
12         C[i][j]=C[i-1][j-1]+C[i-1][j];
13 }
14 inline ll solv(){
15     ll ans=0,tot,tmp;
16     for(int i=1,j,k;i<=len;++i){
17         for(j=0;j<d[i];++j) if(num[j]){
18             --num[j],tmp=1,tot=len-i; //先將這一位填上
19             for(k=0;k<=9;++k) tmp*=C[tot][num[k]],tot-=num[k]; //而後累乘方案數(tot 個空位裏面放 num[k] 個數字)
20             ans+=tmp,++num[j]; //累加答案,而後把填上的數字拿回來
21         } --num[d[i]]; //當前位的數字減掉,作下一位
22     } return ans;
23 }
24 signed main(){
25     scanf("%s",s+1),prep(),len=strlen(s+1); //分解數字
26     for(int i=1;i<=len;++i) ++num[d[i]=s[i]-'0']; //處理每種數字多少個
27     printf("%lld\n",solv()); return 0;
28 }

 

 

題目背景

本題由世界上最蒟蒻最辣雞最撒比的SOL提供。

寂月城網站是完美信息教室的官網。地址:http://191.101.11.174/mgzd 。

題目描述

辣雞蒟蒻SOL是一個傻逼,他竟然以爲數很萌!

好在在他眼裏,並非全部數都是萌的。只有知足「存在長度至少爲2的迴文子串」的數是萌的——也就是說,101是萌的,由於101自己就是一個迴文數;110是萌的,由於包含迴文子串11;可是102不是萌的,1201也不是萌的。

如今SOL想知道從l到r的全部整數中有多少個萌數。

因爲答案可能很大,因此只須要輸出答案對1000000007(10^9+7)的餘數。

輸入輸出格式

輸入格式:

輸入包含僅1行,包含兩個整數:l、r。

 

輸出格式:

輸出僅1行,包含一個整數,即爲答案。

 

分析

咱們能夠從題目中分析出,若是一個數字中存在長度>=2迴文子串,那麼必然存在長度爲 2 或 3 的迴文子串(咱們能夠把迴文子串兩頭反覆去掉,直至長度爲 2 或 3)

接着咱們考慮計算知足的數的個數太麻煩了,因而就能夠計算不知足的數的個數,而後和 tot (總共有多少數)減一減,答案就出來了。

而後咱們在設計一個三維的dp狀態(記錄第 i 位,首位是 j ,次位是 k 的數字有多少個)就能夠開始狀態轉移了。

 

代碼

 1 // luogu-judger-enable-o2
 2 #include<bits/stdc++.h>
 3 #define ll long long
 4 using namespace std;
 5 const int N=1111;
 6 const ll mod=1e9+7;
 7 ll f[N][10][10],ans;
 8 string l,r;
 9 inline void prep(){ //預處理就不解釋了
10     for(int i=2,x,y,z;i<=1000;++i) for(x=0;x<=9;++x)
11         for(y=0;y<=9;++y) if(x!=y){
12             for(z=0;z<=9;++z) if(x!=z &&  y!=z)
13                 f[i][x][y]+=f[i-1][y][z];
14             if(i==2) ++f[i][x][y]; f[i][x][y]%=mod;
15         }
16 } 
17 inline ll solv(string s){
18     int len=s.length(),X=-1,Y=-1; //這裏有點特殊,要記錄前兩個數字
19     ll tot=0,ans=0; bool flag=1;
20     for(int i=0;i<len;++i) tot=(tot*10+s[i]-'0')%mod;
21     for(int i=2,x,y;i<len;++i) for(x=1;x<=9;++x)
22         for(y=0;y<=9;++y) ans=(ans+f[i][x][y])%mod;
23     if(len>1) ans+=10;
24     for(int i=len,j,k;i>1;--i){
25         int now=s[len-i]-'0';
26         for(j=0;j<now;++j) if((i!=len || j!=0) && X!=j && Y!=j )
27             for(k=0;k<=9;++k) if(j!=k && k!=X) ans=(ans+f[i][j][k])%mod;
28         if(now==X || now==Y) { flag=0; break; }  Y=X,X=now; //和 windy 數相似?
29     }
30     if(flag) for(int j=0;j<=s[len-1]-'0';++j)
31         if(j!=Y && j!=X) ans=(ans+1)%mod;
32     return (tot+1+mod-ans)%mod;
33 }
34 int main(){
35     prep(),cin>>l>>r;
36     ans=(solv(r)-solv(l)+mod)%mod;
37     for(int i=1;i<=l.length()-1;++i) //數字太長有兩種解決方法,一是暴力減(根據當前位不夠就向高位借的原則),這裏是暴力算當前數是否知足,知足就累加
38         if(l[i]==l[i-1] || l[i-1]==l[i+1]){
39             ans=(ans+1)%mod; break;
40         }
41     printf("%lld\n",ans);
42 }

 

 

基礎入門是講完了,這裏還有一堆題目呢,感動伐?(唔...別打臉)

loj 第三章:數位動態規劃

作了這些題目以後你會發現被我坑了(明明這些簡單一點嘛 (╯‵□′)╯︵┻━┻ )...

emmm...可是啊...你在切題的同時有沒有點成就感咧?(強行解釋)

 

數位dp 提升訓練

就放兩道題目吧...

  1. 洛谷 P4317 花神的數論題
  2. 洛谷 P4124 [CQOI2016]手機號碼

省選 emmm...(唔...別打臉)

 

1.洛谷 P4317 花神的數論題

題目背景

衆所周知,花神多年來憑藉無邊的神力狂虐各大 OJ、OI、CF、TC …… 固然也包括 CH 啦。

題目描述

話說花神這天又來說課了。課後照例有超級難的神題啦…… 我等蒟蒻又遭殃了。 花神的題目是這樣的:設 \text{sum}(i)sum(i)表示 ii 的二進制表示中 11 的個數。給出一個正整數 NN ,花神要問你 $ \prod_{i=1}^{N}\text{sum}(i)i=1Nsum(i)$ ,也就是 $\text{sum}(1)\sim\text{sum}(N)sum(1)sum(N)$的乘積。

輸入輸出格式

輸入格式:

一個正整數 N。

 

輸出格式:

一個數,答案模 10000007 的值。

 

分析

emmmm...算 sum[1~n] 的乘積。其實仍是蠻常規的(有一道題仍是增強版咧),

數字拆分的時候咱們拆成二進制,而後常規的作,每次往 當前處理的第 i 位後面 i-1 個位子裏面塞 1 就行了,

而後計算塞多少個 1 分別的方案數,最後快速冪一下累乘進答案。

作的時候竟然糾結了半天的逆元盧卡斯什麼的預處理,50暴力遞推組合是就行了啊! 看代碼吧!

 

代碼

 1 // luogu-judger-enable-o2
 2 //by Judge
 3 #include<iostream>
 4 #include<cstdio>
 5 #define ll long long
 6 using namespace std;
 7 const int M=55;
 8 const ll mod=1e7+7;
 9 ll n,len,cnt,ans=1;
10 ll C[M][M],d[M],num[M];
11 inline void prep(){  //預處理組合數模板? 
12     for(int i=0;i<=50;++i) C[i][0]=1;
13     for(int i=1,j;i<=50;++i) for(j=1;j<=50;++j)
14         C[i][j]=C[i-1][j]+C[i-1][j-1];
15 }
16 inline ll quick_pow(ll x,ll p,ll ans=1){  //快速冪模板?
17     while(p){
18         if(p&1) ans=ans*x%mod;
19         x=x*x%mod, p>>=1;
20     } return ans;
21 }
22 signed main(){
23     cin>>n,prep();
24     while(n) d[++len]=n&1,n>>=1;//轉化二進制
25     for(ll i=len,j;i;--i) if(d[i]){
26         for(j=1;j<i;++j) //組合數隨便亂艹
27             num[cnt+j]+=C[i-1][j];
28         ++num[++cnt];
29     }
30     for(ll i=1;i<=len;++i) //直接累乘
31         ans=ans*quick_pow(i,num[i])%mod;
32     cout<<ans<<endl; return 0;
33 }

 

2. 洛谷 P4124 [CQOI2016]手機號碼

題目描述

人們選擇手機號碼時都但願號碼好記、吉利。好比號碼中含有幾位相鄰的相同數字、不含諧音不吉利的數字等。手機運營商在發行新號碼時也會考慮這些因素,從號段中選取含有某些特徵的號碼單獨出售。爲了便於前期規劃,運營商但願開發一個工具來自動統計號段中知足特徵的號碼數量。

工具須要檢測的號碼特徵有兩個:號碼中要出現至少 33 個相鄰的相同數字;號碼中不能同時出現 88 和 44。號碼必須同時包含兩個特徵才知足條件。知足條件的號碼例如:1300098872一、2333333333三、14444101000。而不知足條件的號碼例如:1015400080、10010012022。

手機號碼必定是 11 位數,前不含前導的 00。工具接收兩個數 LL 和 RR,自動統計出 [L,R][L,R] 區間內全部知足條件的號碼數量。LL 和 RR 也是 1111 位的手機號碼。

輸入輸出格式

輸入格式: 

輸入文件內容只有一行,爲空格分隔的 22 個正整數 L,RL,R。

 

輸出格式:

輸出文件內容只有一行,爲 11 個整數,表示知足條件的手機號數量。

 

 

分析

5維狀態直接艹!...第一維記位數,第二維記是哪一個數字,第三維記前面有幾個相同的狀況,第四維記 8 出現過沒,第五維記 4 出現過沒。

 

代碼

 1 //by Judge
 2 #include<iostream>
 3 #include<cstdio>
 4 #define ll long long
 5 using namespace std;
 6 ll L,R,d[15],f[15][15][5][2][2]; // F[ i,j,k,et,fr] 表示第 i 位,前面有 k 個相同,8 是否出現過, 4是否出現過 
 7 inline void prep(){ //預處理 dp 數組
 8     for(int i=0,j,c,et,fr,w;i<=11;++i) for(j=0;j<=9;++j)
 9         for(c=1;c<=3;++c) for(et=0;et<=1;++et) for(fr=0;fr<=1;++fr){
10             ll &t=f[i][j][c][et][fr]; if(!i) t=(c==3?1:0);
11             else for(w=0;w<=9;++w) t+=f[i-1][w][c==3?3:w==j?c+1:1][et||(w==8)][fr||(w==4)];
12             if(et && fr) t=0;
13         }
14 }
15 inline ll solv(ll n){
16     ll len=0,ans=0,c=0,et=0,fr=0;
17     while(n) d[++len]=n%10,n/=10; //分解數字
18     if(len<11) return 0; d[len+1]=-1; //特判 L=1e11 的狀況
19     for(int i=len,j;i;--i){ //暴力枚舉轉移
20         for(j=0;j<d[i];++j) ans+=f[i-1][j][c==3?3:d[i+1]==j?c+1:1][(j==8)||et][(j==4)||fr];
21         c=(c==3?3:d[i]==d[i+1]?c+1:1),et|=d[i]==8;fr|=d[i]==4; if(et && fr) break;
22     } return ans-f[10][0][1][0][0]+f[0][d[1]][c][et][fr];;
23 }
24 signed main(){ prep(),scanf("%lld%lld",&L,&R),printf("%lld\n",solv(R)-solv(L-1)); return 0; } //一行主函數

 

再來幾道題目:

(就四道題,還有我沒作過的... 0.0)

 

代碼以下:

P4127:(...代碼很短,思路很繞)

 1 //by Judge
 2 #include<cstring>
 3 #include<cstdio>
 4 #define ll long long
 5 using namespace std;
 6 ll f[23][200][200][2],d[23],l,r;  //位數,整個數字%sum,各位數字的和%sum,是否達到當前位上界
 7 inline ll solv(ll n){
 8     ll len=0,ans=0,tmp;
 9     while(n) d[++len]=n%10,n/=10;
10     for(int sum=1,i,s,m,c,k;sum<=len*9;++sum){
11         memset(f,0,sizeof(f)), f[len+1][0][0][1]=1;
12         for(i=len;i;--i) for(s=0;s<=sum;++s)
13             for(m=0;m<sum;++m) for(c=0;c<=1;++c){
14             tmp=f[i+1][s][m][c]; if(!tmp) continue;
15             for(k=0;k<=(c?d[i]:9) && s+k<=sum;++k)
16                 f[i][s+k][(m*10+k)%sum][c&(k==d[i])]+=tmp;
17         } ans+=f[1][sum][0][0]+f[1][sum][0][1];
18     } return ans;
19 }
20 signed main(){
21     scanf("%lld%lld",&l,&r);
22     printf("%lld\n",solv(r)-solv(l-1));
23     return 0;
24 }
View Code

 

P3281:(詳情看這裏

 1 //by Judge
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 #define ll long long
 6 #define int long long
 7 using namespace std;
 8 const int M=1e5+111;
 9 const ll mod=20130427;
10 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
11 char buf[1<<21],*p1=buf,*p2=buf;
12 inline int read(){
13     int x=0,f=1; char c=getchar();
14     for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
15     for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
16 }
17 ll B,len,ans,d[M],f[M]={1},sum[M]={1},pre[M],sub[M],dp[M][2];
18 inline void prep(){ for(int i=1,j;i<=1e5;++i) f[i]=f[i-1]*B%mod,sum[i]=(sum[i-1]+f[i])%mod; }
19 inline ll solv(){
20     ll ans=pre[len+1]=0;
21     for(int i=1;i<=len;++i) sub[i]=(d[i]*f[i-1]%mod+sub[i-1])%mod;
22     for(int i=len;i>=1;--i) pre[i]=(pre[i+1]*B%mod+d[i])%mod;
23     for(int i=1;i<=len;++i){  //各類噁心...
24         dp[i][0]=(dp[i-1][0]*B%mod+B*(B-1)/2%mod*f[i-1]%mod*sum[i-1]%mod)%mod;
25         dp[i][1]=(dp[i-1][0]*d[i]%mod+d[i]*(d[i]-1)/2%mod*f[i-1]%mod*sum[i-1]%mod)%mod;
26         dp[i][1]=(dp[i][1]+dp[i-1][1]+d[i]*(sub[i-1]+1)%mod*sum[i-1]%mod)%mod;
27         ans=(ans+max(0ll,pre[i+1]-1)*dp[i][0]%mod+dp[i][1])%mod;
28     } return ans;
29 }
30 signed main(){
31     B=read(),len=read(),prep();
32     for(int i=len;i;--i) d[i]=read();
33     for(int i=1;i<=len;++i)
34         if(d[i]){ --d[i]; break; }
35         else d[i]=B-1;
36     if(!d[len]) --len;
37     ans=-solv(), len=read();
38     for(int i=len;i;--i) d[i]=read();
39     ans+=solv(),printf("%lld\n",(ans%mod+mod)%mod); return 0;
40 }
View Code

 

P2606:假題,不用作(求 n 點小根堆方案數)

 

 1 //by Judge
 2 #include<cstdio>
 3 #include<iostream>
 4 #define fp(i,a,b) for(int i=(a),I=(b)+1;i<I;++i)
 5 #define fd(i,a,b) for(int i=(a),I=(b)-1;i>I;--i)
 6 #define ll long long
 7 using namespace std;
 8 const int M=1e6+3;
 9 ll n,mod,s[M],ans=1,inv[M];
10 int main(){ scanf("%lld%lld",&n,&mod);
11     inv[0]=inv[1]=1; fp(i,1,n) s[i]=1;
12     fp(i,2,n) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
13     fd(i,n,2) s[i>>1]+=s[i];
14     fp(i,1,n) ans=ans*i%mod*inv[s[i]]%mod;
15     return !printf("%lld\n",ans);
16 }
View Code

 

P3286:(不是很懂,瞎抄的)

 1 //by Judge
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 #define ll long long
 6 using namespace std;
 7 const int M=70;
 8 ll L,R,K,num[M],f[M][4000];
 9 ll dfs1(ll n,ll s,bool lim){
10     if(!n) return s;
11     if(!lim && f[n][s]>=0) return f[n][s];
12     ll res=0,k=lim?num[n]:K-1;
13     for(ll i=0;i<=k;++i)
14         res+=dfs1(n-1,s+i*(n-1),lim&&(num[n]==i));
15     if(!lim) f[n][s]=res; return res;
16 }
17 ll dfs2(ll n,ll s,ll m,ll lim){
18     if(s<0) return 0; if(!n) return s;
19     if(!lim && f[n][s]>=0) return f[n][s];
20     ll res=0,k=lim?num[n]:K-1;
21     for(ll i=0;i<=k;++i)
22         if(n>=m) res+=dfs2(n-1,s+i,m,lim&(num[n]==i));
23         else res+=dfs2(n-1,s-i,m,lim&(num[n]==i));
24     if(!lim) f[n][s]=res; return res;
25 }
26 inline ll calc(ll n){
27     ll len=0;
28     memset(f,-1,sizeof(f));
29     while(n) num[++len]=n%K,n/=K;
30     ll res=dfs1(len,0,1);
31     for(ll i=2;i<=len;++i)
32         memset(f,-1,sizeof(f)),
33         res-=dfs2(len,0,i,1);
34     return res;
35 }
36 signed main(){
37     cin>>L>>R>>K, cout<<calc(R)-calc(L-1)<<endl; return 0;
38 }
View Code

 

 

而後再來一道比較有挑戰的?

 

 CF55D: Beautiful number

 

題目就是讓你求 l~r 之間有多少個數是能整除本身各位上的數(排除 0 )

 

而後咱們一看就知道數位 dp ,可是狀態很難設計啊 QWQ 

 

咱們能夠發現全部數位的 lcm 最大爲 2520 (就是 1~ 9 的 lcm 嘛)

 

而後咱們再看就能發現某個數模 2520 下若是能整除 它全部數位的 lcm 那麼它就是知足條件的數

 

也就是說 一個數模其全部數位的 lcm 的結果   模 2520 後再去模這個 lcm 的結果   是相同的

 

爲何?什麼爲何,由於一個數全部數位的 lcm 必然是 2520 的因子啊

 

那麼咱們考慮令 f[i][j][k] 表示還剩 i 位沒有處理,當前的數模 2520 爲 j,當前全部數位的 lcm 爲 k 的方案數,這樣狀態就設計出來了

 

而後就是代碼了:(因爲一開始不會抄的題解,因此寫的是 dfs ,通過幾小時奮鬥終於寫好了本身熟悉的版本——非dfs,因而就放上來了 QVQ )

 

 1 //by Judge
 2 #include<algorithm>
 3 #include<iostream>
 4 #include<cstring>
 5 #include<cstdio>
 6 #define fp(i,a,b) for(int i=(a),I=(b)+1;i<I;++i)
 7 #define fd(i,a,b) for(int i=(a),I=(b)-1;i>I;--i)
 8 #define ll long long
 9 using namespace std;
10 const int mod=2520;
11 #ifndef Judge
12 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
13 #endif
14 char buf[1<<21],*p1=buf,*p2=buf;
15 inline ll read(){ ll x=0,f=1; char c=getchar();
16     for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
17     for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
18 } char sr[1<<21],z[20];int C=-1,Z;
19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
20 inline void print(ll x,char chr='\n'){
21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
22     while(z[++Z]=x%10+48,x/=10);
23     while(sr[++C]=z[Z],--Z);sr[++C]=chr;
24 } int tot,id[mod+50],val[50],d[21];
25 ll f[20][mod+3][50];
26 int GCD(int a,int b) { return b?GCD(b,a%b):a; }
27 int LCM(int a,int b) { return a/GCD(a,b)*b; }
28 void prep() {
29     fp(i,1,mod) if(!(mod%i)) id[i]=++tot,val[tot]=i;
30     fp(i,1,tot) fp(j,0,mod/val[i]) f[0][val[i]*j][i]=1;
31     fp(i,1,19) fp(j,0,mod) fp(k,1,tot) fp(d,0,9)
32         f[i][j][k]+=f[i-1][(j*10+d)%mod][id[d?LCM(val[k],d):val[k]]];
33 }
34 inline ll solv(ll x){
35     int len=0; ll ans=0,Val=0,Lcm=1;
36     for(;x;x/=10) d[++len]=x%10;
37     fd(i,len,1){
38         fp(j,0,d[i]-1) ans+=f[i-1][(Val*10+j)%mod][id[j?LCM(Lcm,j):Lcm]];
39         Val=(Val*10+d[i])%mod,Lcm=d[i]?LCM(Lcm,d[i]):Lcm;
40     } return ans+!(Val%Lcm);
41 }
42 int main(){ prep(); ll T=read(),l,r;
43     fp(kkk,1,T) l=read(),r=read(),
44         print(solv(r)-solv(l-1));
45     return Ot(),0;
46 }
View Code

 

 

 

累死了...寫了兩個多鐘頭...我仍是太弱了)

相關文章
相關標籤/搜索