數位dp總結 之 從入門到模板

基礎篇

數位dp是一種計數用的dp,一般就是要統計一個區間[le,ri]內滿足一些條件數的個數。所謂數位dp,字面意思就是在數位上進行dp咯。數位還算是比較好聽的名字,數位的含義:一個數有個位、十位、百位、千位......數的每一位就是數位啦!

之所以要引入數位的概念完全就是爲了dp。數位dp的實質就是換一種暴力枚舉的方式,使得新的枚舉方式滿足dp的性質,然後記憶化就可以了。

兩種不同的枚舉:對於一個求區間[le,ri]滿足條件數的個數,最簡單的暴力如下:

  1. for(int i=le;i<=ri;i++)  
  2.         if(right(i)) ans++;  

然而這樣枚舉不方便記憶化,或者說根本無狀態可言。

新的枚舉:控制上界枚舉,從最高位開始往下枚舉,例如:ri=213,那麼我們從百位開始枚舉:百位可能的情況有0,1,2(覺得這裏枚舉0有問題的繼續看)

然後每一位枚舉都不能讓枚舉的這個數超過上界213(下界就是0或者1,這個次要),當百位枚舉了1,那麼十位枚舉就是從0到9,因爲百位1已經比上界2小了,後面數位枚舉什麼都不可能超過上界。所以問題就在於:當高位枚舉剛好達到上界是,那麼緊接着的一位枚舉就有上界限制了。具體的這裏如果百位枚舉了2,那麼十位的枚舉情況就是0到1,如果前兩位枚舉了21,最後一位之是0到3(這一點正好對於代碼模板裏的一個變量limit 專門用來判斷枚舉範圍)。最後一個問題:最高位枚舉0:百位枚舉0,相當於此時我枚舉的這個數最多是兩位數,如果十位繼續枚舉0,那麼我枚舉的就是以爲數咯,因爲我們要枚舉的是小於等於ri的所以數,當然不能少了位數比ri小的咯!(這樣枚舉是爲了無遺漏的枚舉,不過可能會帶來一個問題,就是前導零的問題,模板裏用lead變量表示,不過這個不是每個題目都是會有影響的,可能前導零不會影響我們計數,具體要看題目)

由於這種新的枚舉只控制了上界所以我們的Main函數總是這樣:

  1. int main()  
  2. {  
  3.     long long le,ri;  
  4.     while(~scanf("%lld%lld",&le,&ri))  
  5.         printf("%lld\n",solve(ri)-solve(le-1));  
  6. }  
w_w 是吧!統計[1,ri]數量和[1,le-1],然後相減就是區間[le,ri]的數量了,這裏我寫的下界是1,其實0也行,反正相減後就沒了,注意題目中le的範圍都是大於等於1的(不然le=0,再減一就G_G了)

在講例題之前先講個基本的動態模板(先看後面的例題也行):dp思想,枚舉到當前位置pos,狀態爲state(這個就是根據題目來的,可能很多,畢竟dp千變萬化)的數量(既然是計數,dp值顯然是保存滿足條件數的個數)

  1. typedef long long ll;  
  2. int a[20];  
  3. ll dp[20][state];//不同題目狀態不同  
  4. ll dfs(int pos,/*state變量*/,bool lead/*前導零*/,bool limit/*數位上界變量*/)//不是每個題都要判斷前導零  
  5. {  
  6.     //遞歸邊界,既然是按位枚舉,最低位是0,那麼pos==-1說明這個數我枚舉完了  
  7.     if(pos==-1) return 1;/*這裏一般返回1,表示你枚舉的這個數是合法的,那麼這裏就需要你在枚舉時必須每一位都要滿足題目條件,也就是說當前枚舉到pos位,一定要保證前面已經枚舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */  
  8.     //第二個就是記憶化(在此前可能不同題目還能有一些剪枝)  
  9.     if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];  
  10.     /*常規寫法都是在沒有限制的條件記憶化,這裏與下面記錄狀態是對應,具體爲什麼是有條件的記憶化後面會講*/  
  11.     int up=limit?a[pos]:9;//根據limit判斷枚舉的上界up;這個的例子前面用213講過了  
  12.     ll ans=0;  
  13.     //開始計數  
  14.     for(int i=0;i<=up;i++)//枚舉,然後把不同情況的個數加到ans就可以了  
  15.     {  
  16.         if() ...  
  17.         else if()...  
  18.         ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變量傳參都是這樣寫的  
  19.         /*這裏還算比較靈活,不過做幾個題就覺得這裏也是套路了 
  20.         大概就是說,我當前數位枚舉的數是i,然後根據題目的約束條件分類討論 
  21.         去計算不同情況下的個數,還有要根據state變量來保證i的合法性,比如題目 
  22.         要求數位上不能有62連續出現,那麼就是state就是要保存前一位pre,然後分類, 
  23.         前一位如果是6那麼這意味就不能是2,這裏一定要保存枚舉的這個數是合法*/  
  24.     }  
  25.     //計算完,記錄狀態  
  26.     if(!limit && !lead) dp[pos][state]=ans;  
  27.     /*這裏對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裏就是lead就完全不用考慮了*/  
  28.     return ans;  
  29. }  
  30. ll solve(ll x)  
  31. {  
  32.     int pos=0;  
  33.     while(x)//把數位都分解出來  
  34.     {  
  35.         a[pos++]=x%10;//個人老是喜歡編號爲[0,pos),看不慣的就按自己習慣來,反正注意數位邊界就行  
  36.         x/=10;  
  37.     }  
  38.     return dfs(pos-1/*從最高位開始枚舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視爲0嘛  
  39. }  
  40. int main()  
  41. {  
  42.     ll le,ri;  
  43.     while(~scanf("%lld%lld",&le,&ri))  
  44.     {  
  45.         //初始化dp數組爲-1,這裏還有更加優美的優化,後面講  
  46.         printf("%lld\n",solve(ri)-solve(le-1));  
  47.     }  
  48. }  

相信讀者還對這個有不少疑問,筆者認爲有必要講一下記憶化爲什麼是if(!limit)才行,大致就是說有無limit會出現狀態衝突,舉例:

約束:數位上不能出現連續的兩個1(11、112、211都是不合法的)

假設就是[1,210]這個區間的個數

狀態:dp[pos][pre]:當前枚舉到pos位,前面一位枚舉的是pre(更加前面的位已經合法了),的個數(我的pos從0開始)

先看錯誤的方法計數,就是不判limit就是直接記憶化

那麼假設我們第一次枚舉了百位是0,顯然後面的枚舉limit=false,也就是數位上0到9的枚舉,然後當我十位枚舉了1,此時考慮dp[0][1],就是枚舉到個位,前一位是1的個數,顯然dp[0][1]=9;(個位只有是1的時候是不滿足的),這個狀態記錄下來,繼續dfs,一直到百位枚舉了2,十位枚舉了1,顯然此時遞歸到了pos=0,pre=1的層,而dp[0][1]的狀態已經有了即dp[pos][pre]!=-1;此時程序直接return dp[0][1]了,然而顯然是錯的,因爲此時是有limit的個位只能枚舉0,根本沒有9個數,這就是狀態衝突了。有lead的時候可能出現衝突,這只是兩個最基本的不同的題目可能還要加限制,反正宗旨都是讓dp狀態唯一

對於這個錯誤說兩點:一是limit爲true的數並不多,一個個枚舉不會很浪費時間,所以我們記錄下! limit的狀態解決了不少子問題重疊。第二,有人可能想到把dp狀態改一下dp[pos][state][limit]就是分別記錄不同limit下的個數,這種方法一般是對的,關於這個具體會講,下面有題bzoj3209會用到這個。

數位的部分就是這些,然後就是難點,dp部分,dp大牛的藝術,弱雞隻能看看+...+

既然從高位往低位枚舉,那麼狀態一般都是與前面已經枚舉的數位有關並且通常是根據約束條件當前枚舉的這一位能使得狀態完整(比如一個狀態涉及到連續k位,那麼就保存前k-1的狀態,當前枚舉的第k個是個恰好湊成成一個完整的狀態,不過像那種狀態是數位的和就直接保存前綴和爲狀態了),不過必然有一位最簡單的一個狀態dp[pos]當前枚舉到了pos位。dp部分就要開始講例題了,不過會介紹幾種常用防tle的優化。

實戰篇

例一:HDU 2089 不要62
入門題。就是數位上不能有4也不能有連續的62,沒有4的話在枚舉的時候判斷一下,不枚舉4就可以保證狀態合法了,所以這個約束沒有記憶化的必要,而對於62的話,涉及到兩位,當前一位是6或者不是6這兩種不同情況我計數是不相同的,所以要用狀態來記錄不同的方案數。
dp[pos][sta]表示當前第pos位,前一位是否是6的狀態,這裏sta只需要去0和1兩種狀態就可以了,不是6的情況可視爲同種,不會影響計數。
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<string>  
  5. using namespace std;  
  6. typedef long long ll;  
  7. int a[20];  
  8. int dp[20][2];  
  9. int dfs(int pos,int pre,int sta,bool limit)  
  10. {  
  11.     if(pos==-1) return 1;  
  12.     if(!limit && dp[pos][sta]!=-1) return dp[pos][sta];  
  13.     int up=limit ? a[pos] : 9;  
  14.     int tmp=0;  
  15.     for(int i=0;i<=up;i++)  
  16.     {  
  17.         if(pre==6 && i==2)continue;  
  18.         if(i==4) continue;//都是保證枚舉合法性  
  19.         tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);  
  20.     }  
  21.     if(!limit) dp[pos][sta]=tmp;  
  22.     return tmp;  
  23. }  
  24. int solve(int x)  
  25. {  
  26.     int pos=0;  
  27.     while(x)  
  28.     {  
  29.         a[pos++]=x%10;  
  30.         x/=10;  
  31.     }  
  32.     return dfs(pos-1,-1,0,true);  
  33. }  
  34. int main()  
  35. {  
  36.     int le,ri;  
  37.     //memset(dp,-1,sizeof dp);可優化  
  38.     while(~scanf("%d%d",&le,&ri) && le+ri)  
  39.     {  
  40.         memset(dp,-1,sizeof dp);  
  41.         printf("%d\n",solve(ri)-solve(le-1));  
  42.     }  
  43.     return 0;  
  44. }  

入門就不多講了,開始講常用優化吧!

第一:memset(dp,-1,sizeof dp);放在多組數據外面。

這一點是一個數位特點,使用的條件是:約束條件是每個數自身的屬性,而與輸入無關。
具體的:上一題不要62和4,這個約束對每一個數都是確定的,就是說任意一個數滿不滿足這個約束都是確定,比如444這個數,它不滿足約束條件,不管你輸入的區間是多少你都無法改變這個數不滿足約束這個事實,這就是數自身的屬性(我們每組數據只是在區間計數而已,只能說你輸入的區間不包含444的話,我們就不把它統計在內,而無法改變任何事實)。
由此,我們保存的狀態就可以一直用(注意還有要limit,不同區間是會影響數位在有限制條件下的上限的)
這點優化就不給具體題目了,這個還有進一步的擴展。不過說幾個我遇到的簡單的約束:
1.求數位和是10的倍數的個數,這裏簡化爲數位sum%10這個狀態,即dp[pos][sum]這裏10 是與多組無關的,所以可以memset優化,不過注意如果題目的模是輸入的話那就不能這樣了。
2.求二進制1的數量與0的數量相等的個數,這個也是數自身的屬性。
3.。。。。。
還是做題積累吧。搞懂思想!
下面介紹的方法就是要行memset優化,把不滿足前提的通過修改,然後優化。
介紹之前,先說一種較爲笨拙的修改,那就是增加狀態,前面講limit的地方說增加一維dp[pos][state][limit],能把不同情況下狀態分別記錄(不過這個不能memset放外面)。基於這個思想,我們考慮:約束爲數位是p的倍數的個數,其中p數輸入的,這和上面sum%10類似,但是dp[pos][sum]顯然已經不行了,每次p可能都不一樣,爲了強行把memset提到外面加狀態dp[pos][sum][p],對於每個不同p分別保存對應的狀態。這裏前提就比較簡單了,你dp數組必須合法,p太大就G_G了。所以對於與輸入有關的約束都可以強行增加狀態(這並不代表能ac,如果題目數據少的話就隨便你亂搞了)

第二:相減。

例題:HDU 4734
題目給了個f(x)的定義:F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,Ai是十進制數位,然後給出a,b求區間[0,b]內滿足f(i)<=f(a)的i的個數。
常規想:這個f(x)計算就和數位計算是一樣的,就是加了權值,所以dp[pos][sum],這狀態是基本的。a是題目給定的,f(a)是變化的不過f(a)最大好像是4600的樣子。如果要memset優化就要加一維存f(a)的不同取值,那就是dp[10][4600][4600],這顯然不合法。
這個時候就要用減法了,dp[pos][sum],sum不是存當前枚舉的數的前綴和(加權的),而是枚舉到當前pos位,後面還需要湊sum的權值和的個數,
也就是說初始的是時候sum是f(a),枚舉一位就減去這一位在計算f(i)的權值,那麼最後枚舉完所有位 sum>=0時就是滿足的,後面的位數湊足sum位就可以了。
仔細想想這個狀態是與f(a)無關的(新手似乎很難理解),一個狀態只有在sum>=0時才滿足,如果我們按常規的思想求f(i)的話,那麼最後sum>=f(a)纔是滿足的條件。
  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<iostream>  
  4. #include<string>  
  5.   
  6. using namespace std;  
  7. const int N=1e4+5;  
  8. int dp[12][N];  
  9. int f(int x)  
  10. {  
  11.     if(x==0) return 0;  
  12.     int ans=f(x/10);  
  13.     return ans*2+(x%10);  
  14. }  
  15. int all;  
  16. int a[12];  
  17. int dfs(int pos,int sum,bool limit)  
  18. {  
  19.     if(pos==-1) {return sum<=all;}  
  20.     if(sum>all) return 0;  
  21.     if(!limit && dp[pos][all-sum]!=-1) return dp[pos][all-sum];  
  22.     int up=limit ? a[pos] : 9;  
  23.     int ans=0;  
  24.     for(int i=0;i<=up;i++)  
  25.     {  
  26.         ans+=dfs(pos-1,sum+i*(1<<pos),limit && i==a[pos]);  
  27.     }  
  28.     if(!limit) dp[pos][all-sum]=ans;  
  29.     return ans;  
  30. }  
  31. int solve(int x)  
  32. {  
  33.     int pos=0;  
  34.     while(x)  
  35.     {  
  36.         a[pos++]=x%10;  
  37.         x/=10;  
  38.     }  
  39.     return dfs(pos-1,0,true);  
  40. }  
  41. int main()  
  42. {  
  43.     int a,ri;  
  44.     int T_T;  
  45.     int kase=1;  
  46.     scanf("%d",&T_T);  
  47.     memset(dp,-1,sizeof dp);  
  48.     while(T_T--)  
  49.     {  
  50.         scanf("%d%d",&a,&ri);  
  51.         all=f(a);  
  52.         printf("Case #%d: %d\n",kase++,solve(ri));  
  53.     }  
  54.     return 0;  
  55. }  

減法的藝術!!!

例題 POJ 3252
這題的約束就是一個數的二進制中0的數量要不能少於1的數量,通過上一題,這題狀態就很簡單了,dp[pos][num],到當前數位pos,0的數量減去1的數量不少於num的方案數,一個簡單的問題,中間某個pos位上num可能爲負數(這不一定是非法的,因爲我還沒枚舉完嘛,只要最終的num>=0才能判合法,中途某個pos就不一定了),這裏比較好處理,Hash嘛,最小就-32吧(好像),直接加上32,把32當0用。這題主要是要想講一下lead的用法,顯然我要統計0的數量,前導零是有影響的。至於!lead&&!limit才能dp,都是類似的,自己慢慢體會吧。
  1. #pragma comment(linker, "/STACK:10240000,10240000")  
  2. #include<iostream>  
  3. #include<cstdio>  
  4. #include<cstring>  
  5. #include<string>  
  6. #include<queue>  
  7. #include<set>  
  8. #include<vector>  
  9. #include<map>  
  10. #include<stack>  
  11. #include<cmath>  
  12. #include<algorithm>  
  13. using namespace std;  
  14. const double R=0.5772156649015328606065120900;  
  15. const int N=1e5+5;  
  16. const int mod=1e9+7;  
  17. const int INF=0x3f3f3f3f;  
  18. const double eps=1e-8;  
  19. const double pi=acos(-1.0);  
  20. typedef long long ll;  
  21. int dp[35][66];  
  22. int a[66];  
  23. int dfs(int pos,int sta,bool lead,bool limit)  
  24. {  
  25.     if(pos==-1)  
  26.         return sta>=32;  
  27.     if(!limit && !lead && dp[pos][sta]!=-1) return dp[pos][sta];  
  28.     int up=limit?a[pos]:1;  
  29.     int ans=0;  
  30.     for(int i=0;i<=up;i++)  
  31.     {  
  32.         if(lead && i==0) ans+=dfs(pos-1,sta,lead,limit && i==a[pos]);//有前導零就不統計在內  
  33.         else ans+=dfs(pos-1,sta+(i==0?1:-1),lead && i==0,limit && i==a[pos]);  
  34.     }  
  35.     if(!limit && !lead ) dp[pos][sta]=ans;  
  36.     return ans;  
  37. }  
  38. int solve(int x)  
  39. {  
  40.     int pos=0;  
  41.     while(x)  
  42.     {  
  43.         a[pos++]=x&1;  
  44.         x>>=1;  
  45.     }  
  46.     return dfs(pos-1,32,true,true);  
  47. }  
  48. int main()  
  49. {  
  50.     memset(dp,-1,sizeof dp);  
  51.     int a,b;  
  52.     while(~scanf("%d%d",&a,&b))  
  53.     {  
  54.         printf("%d\n",solve(b)-solve(a-1));  
  55.     }  
  56.     return 0;  
  57. }  
然後就是一些需要自己yy的題:
HDU 3709 這題就是要枚舉中軸,然後數位dp
UVA 1305 這題我二分然後數位dp搞(好像不是正解,我水過的)
Hbzoj 1799 這題講一下:
(偷偷告訴你,這個oj是單組測試,然後memset什麼的都是浮雲了)
約束:一個數是它自己數位和的倍數,直接dp根本找不到狀態,枚舉數位和,因爲總就162,然後問題就變成了一個數%mod=0,mod是枚舉的,想想狀態:dp[pos][sum][val],當前pos位上數位和是sum,val就是在算這個數%mod,(從高位算  *10+i),因爲我們枚舉的數要保證數位和等於mod,還要保證這個數是mod的倍數,很自然就能找到這些狀態,顯然對於每一個mod,val不能保證狀態唯一,這是你要是想加一維dp[pos][sum][val][mod],記錄每一個mod的狀態(這裏sum可以用減法,然而val不行,就只能加一維),那你就想太多了,這樣是會超時的(因爲狀態太多,記憶化效果不好)。這裏直接對每一個mod,memset一次就能ac。下面的代碼還把limit的當做了狀態,因爲每次都要初始化,所以能這樣,memset在多組外面是不能這樣的,不過奇葩的,這代碼,如果不把limit當狀態,還是在!limit 條件下記錄dp,提交一發,時間竟然更短了,可能是每次memset的關係!!!
  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<iostream>  
  4. #include<string>  
  5.   
  6. using namespace std;  
  7.   
  8. typedef long long ll;  
  9.   
  10. ll dp[20][163][163][2];  
  11. int a[20];  
  12. ll dfs(int pos,int sum,int val,int mod,bool limit)  
  13. {  
  14.     if(sum-9*pos-9>0) return 0;//最壞的情況,這一位及後面的全部爲9都不能達到0那就直接GG,這個剪枝不會影響ac  
  15.     if(pos==-1) return sum==0 && val==0;  
  16.     if(dp[pos][sum][val][limit]!=-1) return dp[pos][sum][val][limit];  
  17.     int up=limit?a[pos]:9;  
  18.     ll ans=0;  
  19.     for(int i=0;i<=up;i++)  
  20.     {  
  21.         if(sum-i<0) break;  
  22.         ans+=dfs(pos-1,sum-i,(val*10+i)%mod,mod,limit && i==a[pos]);  
  23.     }  
  24.     dp[pos][sum][val][limit]=ans;  
  25.     return ans;  
  26. }  
  27. ll solve(ll x)  
  28. {  
  29.     int pos=0;  
  30.     while(x)  
  31.     {  
  32.         a[pos++]=x%10;  
  33.         x/=10;  
  34.     }  
  35.     ll ans=0;  
  36.     for(int i=1;i<=pos*9;i++)//上限就是每一位都是9  
  37.     {  
  38.         memset(dp,-1,sizeof dp);  
  39.         ll tmp=dfs(pos-1,i,0,i,true);  
  40.         ans+=tmp;  
  41.     }  
  42.     return ans;  
  43. }  
  44. int main()  
  45. {  
  46. //    cout<<18*9<<endl;  
  47.     ll le,ri;  
  48. //    memset(dp,-1,sizeof dp);  
  49.     while(~scanf("%lld%lld",&le,&ri))  
  50.         printf("%lld\n",solve(ri)-solve(le-1));  
  51.     return 0;  
  52. }  
  53. /* 
  54. 1 1000000000000000000 
  55. */  
基本講的差不多了。前段時間學了點新東西!!

新的領域--計數轉求和

這題麻煩就是要求數的平方和。
我們先考慮求和的問題,一個區間,數位dp能在一些約束下計數,現在要這些數的和。其實組合數學搞搞就可以了:如 現在枚舉的某一位pos,我統計了這一位枚舉i的滿足條件的個數cnt,其實只要算i對總和的貢獻就可以了,對於一個數而言第pos位是i,那麼對求和貢獻就是i*10^pos,就是十進制的權值,然後有cnt個數都滿足第pos位是i,最後sum=cnt*i*10^pos.原理就是這樣平方和可以看做(a*10^pos+b)^2,a是你當前pos位要枚舉的,b其實是個子問題,就是pos之後的位的貢獻值,把這個平方展開就可以了!
  1. #pragma comment(linker, "/STACK:10240000,10240000")  
  2. #include<iostream>  
  3. #include<cstdio>  
  4. #include<cstring>  
  5. #include<string>  
  6. #include<queue>  
  7. #include<set>  
  8. #include<vector>  
  9. #include<map>  
  10. #include<stack>  
  11. #include<cmath>  
  12. #include<algorithm>  
  13. using namespace std;  
  14. const double R=0.5772156649015328606065120900;  
  15. const int N=1e5+5;  
  16. const int mod=1e9+7;  
  17. const int INF=0x3f3f3f3f;  
  18. const double eps=1e-8;  
  19. const double pi=acos(-1.0);  
  20. typedef long long ll;  
  21. ll fact[20];  
  22. void init()  
  23. {  
  24.     fact[0]=1;  
  25.     for(int i=1;i<20;i++)  
  26.         fact[i]=fact[i-1]*10%mod;  
  27. }  
  28. struct node  
  29. {  
  30.     ll cnt,sum,sqr;  
  31.     node(ll cnt=-1,ll sum=0,ll sqr=0):cnt(cnt),sum(sum),sqr(sqr){}  
  32. }dp[20][7][7];  
  33. int a[20];  
  34. ll fac(ll x)  
  35. {  
  36.     return x*x%mod;  
  37. }  
  38. ll dfs(int pos,ll num,ll val,ll&cnt,ll&sum,bool limit)  
  39. {  
  40.     if(pos==-1) {  
  41.         if(num==0 || val==0)  
  42.             return 0;  
  43.         cnt=1;  
  44.         return 0;  
  45.     }  
  46.     if(!limit && dp[pos][num][val].cnt!=-1) {  
  47.             cnt=dp[pos][num][val].cnt;  
  48.             sum=dp[pos][num][val].sum;  
  49.             return dp[pos][num][val].sqr;  
  50.     }  
  51.     int up=limit?a[pos]:9;  
  52.     ll sq=0;  
  53.     for(int i=0;i<=up;i++)  
  54.     if(i!=7)  
  55.     {  
  56.         ll cn=0,su=0;  
  57.         ll tmp=dfs(pos-1,(num+i)%7,(val*10+i)%7,cn,su,limit && i==a[pos]);  
  58.         ll tm=i*fact[pos]%mod;  
  59.         tmp=(tmp+fac(tm)*cn%mod+(tm*su%mod)*2%mod)%mod;//計數之後要更新sum,sqr  
  60.         sum=(sum+su+(i*fact[pos]%mod)*cn%mod)%mod;  
  61.         cnt=(cnt+cn)%mod;  
  62.         sq=(sq+tmp)%mod;  
  63.     }  
  64.     if(!limit) dp[pos][num][val]=node(cnt,sum,sq);  
  65.     return sq;  
  66. }  
  67. ll solve(ll x)  
  68. {  
  69.     int pos=0;  
  70.     while(x)  
  71.     {  
  72.         a[pos++]=x%10;  
  73.         x/=10;  
  74.     }  
  75.     ll t1=0,t2=0;  
  76.     return dfs(pos-1,0,0,t1,t2,true);  
  77. }  
  78. bool judge(ll x)  
  79. {  
  80.     int sum=0;  
  81.     int pos=0;  
  82.     if(x%7==0) return false;  
  83.     while(x)  
  84.     {  
  85.         if(x%10==7) return false;  
  86.         sum+=x%10;  
  87.         x/=10;  
  88.     }  
  89.     sum%=7;  
  90.     return sum!=0;  
  91. }  
  92. int main()  
  93. {  
  94.     init();  
  95.     for(int i=0;i<20;i++)  
  96.         for(int j=0;j<7;j++)  
  97.         for(int k=0;k<7;k++)//memset  
  98.     {  
  99.         dp[i][j][k].cnt=-1;  
  100.         dp[i][j][k].sum=0;  
  101.         dp[i][j][k].sqr=0;  
  102.     }  
  103.     int T_T;  
  104.     scanf("%d",&T_T);  
  105.     while(T_T--)  
  106.     {  
  107.         ll le,ri;  
  108.         scanf("%I64d%I64d",&le,&ri);  
  109.         ll ans=solve(ri)-solve(le-1);  
  110.         ans=(ans%mod+mod)%mod;  
  111.         printf("%I64d\n",ans);  
  112.     }  
  113.     return 0;  
  114. }  
做題去~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章