1, HDU 2089 不要62 :http://acm.hdu.edu.cn/showproblem.php?pid=2089
題意:不能出現4,或者相鄰的62,
dp[i][0],表示不存在不吉利數字
dp[i][1],表示不存在不吉利數字,且最高位為2
dp[i][2],表示存在不吉利數字

#include<iostream> #include<cstdio> #include<cstring> using namespace std; int dp[10][3]; void Init(){ //預處理,算出所有可能 memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1;i<=8;i++){ dp[i][0]=dp[i-1][0]*9-dp[i-1][1]; //在不含不吉利數62和4的首位分別補除了4的9個數字,減去在2前面補6的個數 dp[i][1]=dp[i-1][0]; //在不含不吉利數在首位補2 dp[i][2]=dp[i-1][2]*10+dp[i-1][0]+dp[i-1][1]; //各種出現不吉利數的情況 } } int Solve(int x){ int digit[15]; int cnt=0,tmp=x; while(tmp){ digit[++cnt]=tmp%10; tmp/=10; } digit[cnt+1]=0; int flag=0,ans=0; for(int i=cnt;i>0;i--){ ans+=digit[i]*dp[i-1][2]; //由上位所有不吉利數推導 if(flag) //之前出現不吉利的數字 ans+=digit[i]*dp[i-1][0]; else{ if(digit[i]>4) //出現4 ans+=dp[i-1][0]; if(digit[i]>6) //出現6 ans+=dp[i-1][1]; if(digit[i+1]==6 && digit[i]>2) //出現62 ans+=dp[i][1]; } if(digit[i]==4 || (digit[i+1]==6 && digit[i]==2)) flag=1; } return x-ans; //所有的數減去不吉利的數 } int main(){ //freopen("input.txt","r",stdin); int a,b; Init(); while(~scanf("%d%d",&a,&b)){ if(a==0 && b==0) break; printf("%d\n",Solve(b+1)-Solve(a)); } return 0; }
另附一種暴力預處理法:
string 類提供了 6 種查找函數,每種函數以不同形式的 find 命名。這些操作全都返回 string::size_type 類型的值,以下標形式標記查找匹配所發生的位置;或者返回一個名為 string::npos 的特殊值,說明查找沒有匹配。string 類將 npos 定義為保證大於任何有效下標的值。
比如:
string str;
pos=str.find_first_of("h");
if(pos!=string::npos)
{..
....
} //npos是一個常數,用來表示不存在的位置,類型一般是std::container_type::size_type
//許多容器都提供這個東西。取值由實現決定,一般是-1,這樣做,就不會存在移植的問題了。npos表示string的結束位子,
//是string::type_size 類型的,也就是find()返回的類型。

#include<iostream> #include<cstdio> #include<cstring> #include<string> using namespace std; int n,m; int lucky[1000010]; void Init(){ char c[20]; string str; for(int i=1;i<1000000;i++){ sprintf(c,"%d",i); str=c; if(str.find("62")==string::npos && str.find("4")==string::npos) lucky[i]=1; else lucky[i]=0; } } int main(){ //freopen("input.txt","r",stdin); int ans; Init(); while(~scanf("%d%d",&n,&m)){ if(n==0 && m==0) break; ans=0; for(int i=n;i<=m;i++) ans+=lucky[i]; printf("%d\n",ans); } return 0; }
2, HDU 3555 Bomb:http://acm.hdu.edu.cn/showproblem.php?pid=3555
題意就是找0到n有多少個數中含有49。數據范圍接近10^20
DP的狀態是2維的dp[len][3]
dp[len][0] 代表長度為len不含49的方案數
dp[len][1] 代表長度為len不含49但是以9開頭的數字的方案數
dp[len][2] 代表長度為len含有49的方案數
狀態轉移如下
dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1]; // not include 49 如果不含49且,在前面可以填上0-9 但是要減去dp[i-1][1] 因為4會和9構成49
dp[i][1] = dp[i-1][0]; // not include 49 but starts with 9 這個直接在不含49的數上填個9就行了
dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1]; // include 49 已經含有49的數可以填0-9,或者9開頭的填4
接着就是從高位開始統計
在統計到某一位的時候,加上 dp[i-1][2] * digit[i] 是顯然對的,因為這一位可以填 0 - (digit[i]-1)
若這一位之前挨着49,那么加上 dp[i-1][0] * digit[i] 也是顯然對的。
若這一位之前沒有挨着49,但是digit[i]比4大,那么當這一位填4的時候,就得加上dp[i-1][1]

#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long n,dp[25][3]; void Init(){ memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1;i<=20;i++){ dp[i][0]=dp[i-1][0]*10-dp[i-1][1]; dp[i][1]=dp[i-1][0]; dp[i][2]=dp[i-1][2]*10+dp[i-1][1]; } } long long Solve(long long x){ int digit[25]; int cnt=0; while(x){ digit[++cnt]=x%10; x/=10; } digit[cnt+1]=0; int flag=0; long long ans=0; for(int i=cnt;i>0;i--){ ans+=digit[i]*dp[i-1][2]; if(flag) ans+=digit[i]*dp[i-1][0]; else{ if(digit[i]>4) ans+=dp[i-1][1]; } if(digit[i+1]==4 && digit[i]==9) flag=1; } return ans; } int main(){ //freopen("input.txt","r",stdin); int t; scanf("%d",&t); Init(); while(t--){ scanf("%I64d",&n); printf("%I64d\n",Solve(n+1)); //因為包含n,所以n需要+1 } return 0; }
DFS:

#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; long long n,dp[25][3]; int digit[25]; long long DFS(int pos,int status,int limit){ if(pos==-1) // 如果到了已經枚舉了最后一位,並且在枚舉的過程中有49序列出現 return status==2; if(!limit && dp[pos][status]!=-1) // 對於有限制的詢問我們是不能夠記憶化的 return dp[pos][status]; long long ans=0; int s,end=limit?digit[pos]:9; // 確定這一位的上限是多少 for(int i=0;i<=end;i++){ // 每一位有這么多的選擇 s=status; // 有點else s = statu 的意思 if(status==1 && i==9) s=2; if(status==0 && i==4) s=1; if(status==1 && i!=4 && i!=9) s=0; ans+=DFS(pos-1,s,limit && i==end); } if(!limit) dp[pos][status]=ans; return ans; } long long Cal(long long x){ int cnt=-1; while(x){ digit[++cnt]=x%10; x/=10; } return DFS(cnt,0,1); } int main(){ //freopen("input.txt","r",stdin); int t; scanf("%d",&t); while(t--){ memset(dp,-1,sizeof(dp)); scanf("%I64d",&n); printf("%I64d\n",Cal(n)); } return 0; }
3, UESTC 1307 windy數 : http://acm.uestc.edu.cn/problem.php?pid=1307
要求相鄰的數差大於等於2

#include<iostream> #include<cstdio> #include<cstring> using namespace std; int dp[20][10]; //dp[i][j]表示考慮i位的數中,最高為j的windy數 int abs(int x){ return x<0?-x:x; } void Init(){ memset(dp,0,sizeof(dp)); for(int i=0;i<=9;i++) dp[1][i]=1; for(int i=2;i<=10;i++) for(int j=0;j<10;j++) for(int k=0;k<10;k++) if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k]; } int Solve(int x){ int digit[20],cnt=0; while(x){ digit[++cnt]=x%10; x/=10; } digit[cnt+1]=0; int ans=0; for(int i=1;i<cnt;i++) //先把長度為1至cnt-1計入 for(int j=1;j<10;j++) ans+=dp[i][j]; for(int j=1;j<digit[cnt];j++) //確定最高位 ans+=dp[cnt][j]; for(int i=cnt-1;i>0;i--){ for(int j=0;j<digit[i];j++) if(abs(j-digit[i+1])>=2) ans+=dp[i][j]; if(abs(digit[i]-digit[i+1])<2) //如果高位已經出現非法,直接退出 break; } return ans; } int main(){ //freopen("input.txt","r",stdin); int a,b; Init(); while(~scanf("%d%d",&a,&b)){ printf("%d\n",Solve(b+1)-Solve(a)); } return 0; }
4, HDU 3652 B-number :http://acm.hdu.edu.cn/showproblem.php?pid=3652
題意:求小於n是13的倍數且含有'13'的數的個數
dp[i][j][k]
i:第i位,j:余數為j
k=0:不含13 1:3開頭不含13 2:含13

#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=12; int md[N],dp[N][13][3]; void Init(){ md[0]=1; for(int i=1;i<N;i++) md[i]=md[i-1]*10%13; memset(dp,0,sizeof(dp)); dp[0][0][0]=1; for(int i=0;i<N-1;i++) for(int j=0;j<13;j++){ for(int k=0;k<10;k++) dp[i+1][(j+md[i]*k)%13][0]+=dp[i][j][0]; dp[i+1][(j+md[i])%13][0]-=dp[i][j][1]; dp[i+1][(j+md[i]*3)%13][1]+=dp[i][j][0]; dp[i+1][(j+md[i])%13][2]+=dp[i][j][1]; for(int k=0;k<10;k++) dp[i+1][(j+md[i]*k)%13][2]+=dp[i][j][2]; } } int Solve(int x){ int digit[15],len=0; while(x){ digit[len++]=x%10; x/=10; } digit[len]=0; int flag=0,ans=0,mod=0; for(int i=len-1;i>=0;mod=(mod+digit[i]*md[i])%13,i--){ for(int j=0;j<digit[i];j++) ans+=dp[i][(13-(mod+j*md[i])%13)%13][2]; if(flag){ for(int j=0;j<digit[i];j++) ans+=dp[i][(13-(mod+j*md[i])%13)%13][0]; }else{ if(digit[i+1]==1 && digit[i]>3) ans+=dp[i+1][(13-mod)%13][1]; if(digit[i]>1) ans+=dp[i][(13-(mod+md[i])%13)%13][1]; } if(digit[i+1]==1 && digit[i]==3) flag=1; } return ans; } int main(){ //freopen("input.txt","r",stdin); Init(); int n; while(~scanf("%d",&n)){ printf("%d\n",Solve(n+1)); } return 0; }
另附DFS:

#include<iostream> #include<cstdio> #include<cstring> using namespace std; int dp[20][13][3]; int n,digit[20]; int DFS(int pos,int mod,int status,int limit){ //limit 有上限為1 無上限為0 if(pos<=0) return status==2 && mod==0; if(!limit && dp[pos][mod][status]!=-1) //當前狀態訪問過,沒有上限 return dp[pos][mod][status]; int end=limit?digit[pos]:9; int ans=0; for(int i=0;i<=end;i++){ int nmod=(mod*10+i)%13; int nstatus=status; if(status==0 && i==1) //高位不含13,並且末尾不是1 ,現在末尾添1 nstatus=1; if(status==1 && i!=1) //高位不含13,且末位是1,現在末尾添加的不是1返回0狀態 nstatus=0; if(status==1 && i==3) //高位不含13,且末尾是1,現在末尾添加3返回2狀態 nstatus=2; ans+=DFS(pos-1,nmod,nstatus,limit && i==end); } if(!limit) dp[pos][mod][status]=ans; return ans; } int main(){ //freopen("input.txt","r",stdin); memset(dp,-1,sizeof(dp)); while(~scanf("%d",&n)){ int len=0; while(n){ digit[++len]=n%10; n/=10; } digit[len+1]=0; printf("%d\n",DFS(len,0,0,1)); } return 0; }
5,HDU 3943 K-th Nya Number :http://acm.hdu.edu.cn/showproblem.php?pid=3943
用X個4和Y個7的數為規定的數,然后就是區間統計。
先預處理好dp[i][j][k]表示I位的數中有j個4和k個7的數量。
之后就可以通過高位開始枚舉,求出區間內有多少個規定的數,如果詢問大於總數,則輸出"Nya!";
之后是怎么找到第K大數。
首先可以確定出位數,dp[i][x][y]表示i位時的滿足數,那么大於dp[len-1][x][y]而小於dp[len][x][y],len表示目標位數。
確定了位數之后,依舊從高位開始。比如說高位首先是0,而dp[len-1][x][y]小於k,說明0開頭的目標說小於所求,所以往后繼續找,記得要把之前的減掉。
還得注意一些細節,出現了4和7的情況。
貌似有題解說的是二分查找,沒有過多的了解。
另外坑的是 這里的區間是左開右閉。

#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long dp[25][25][25]; //dp[i][j][k]表示i位的數,有j個4,k個7的數量 long long p,q; int x,y; void Init(){ memset(dp,0,sizeof(dp)); dp[0][0][0]=1; for(int i=1;i<21;i++) for(int j=0;j<=i;j++) for(int k=0;k<=i;k++) if(j+k<=i){ dp[i][j][k+1]+=dp[i-1][j][k]; dp[i][j+1][k]+=dp[i-1][j][k]; dp[i][j][k]+=dp[i-1][j][k]*8; //在高位加上除了4、7以外的8個數字 } } long long getCount(long long n){ int digit[25],len=0; while(n){ digit[++len]=n%10; n/=10; } digit[len+1]=0; long long ans=0; int cx=x,cy=y; for(int i=len;i>0;i--){ //從高位開始枚舉 for(int j=0;j<digit[i];j++){ if(j==4){ if(cx) ans+=dp[i-1][cx-1][cy]; }else if(j==7){ if(cy) ans+=dp[i-1][cx][cy-1]; }else ans+=dp[i-1][cx][cy]; } if(digit[i]==4) cx--; if(digit[i]==7) cy--; if(cx<0 || cy<0) //如果高位出現的4、7數量已經超過要求,則退出 break; } return ans; } long long Solve(long long k){ int len=1; while(1){ if(dp[len-1][x][y]<k && dp[len][x][y]>=k) //找到目標數的長度 break; len++; } long long res=0; int cx=x,cy=y; for(int i=len;i>0;i--) //從高位開始從小枚舉 for(int j=0;j<10;j++){ int tx=cx,ty=cy; if(j==4){ tx--; if(tx<0) continue; } if(j==7){ ty--; if(ty<0) continue; } if(dp[i-1][tx][ty]>=k){ res=res*10+j; cx=tx; cy=ty; break; } k-=dp[i-1][tx][ty]; } return res; } int main(){ //freopen("input.txt","r",stdin); int t,cases=0; scanf("%d",&t); Init(); while(t--){ scanf("%I64d%I64d%d%d",&p,&q,&x,&y); long long a=getCount(q+1); long long b=getCount(p+1); //注意是左開區間, int n; long long k; scanf("%d",&n); printf("Case #%d:\n",++cases); while(n--){ scanf("%I64d",&k); if(k>a-b) puts("Nya!"); else printf("%I64d\n",Solve(k+b)); } } return 0; }
6, HDU 3709 Balance Number : http://acm.hdu.edu.cn/showproblem.php?pid=3709
平衡數,枚舉支點,然后其它的類似。加一維表示當前的力矩,注意當力矩為負時,就要返回,否則會出現下標為負,也算是個剪枝。
題目大意: 題目先給出平衡數的概念:數n以數n中的某個位為支點,每個位上的數權值為(數字xi*(posi - 支點的posi)),如果數n里有一個支點使得所有數權值之和為0那么她就是平衡數。比如4139,以3為支點,左邊 = 4 * (4 - 2) + 1 * (3 - 2) = 9,右邊 = 9 * (1 - 2) = -9,左邊加右邊為0,所以4139是平衡數。現在給出一個區間[l,r],問區間內平衡數有多少個?
解題思路:
這類題目用逆推要比正推好做,方法是記憶化搜索。每次向下傳遞目前的狀態,下面每次都返回通過這些狀態后面能得到的結果。
因為要權值之和為0,我們枚舉每個支點o,然后從高位往地位搜索並記錄狀態,這里的狀態為當前的位置pos,之前的權值之和pre、支點o,這三個組合起來就可以表示一個狀態。每種狀態都至多遍歷一次,如果第二次遍歷到某個狀態,就直接返回前一次往下遍歷的結果。
上一段已經解釋了Dfs中的三個參數,那還剩下一個參數limit是干什么的?limit表示是否有上界,如果我們要找的是[0,12345,現在找到123,這時limit還是1,如果下一個枚舉到的數是3,limit就變成0,以后都可以枚舉到9而不是到5.

#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long dp[20][20][2010]; //dp記憶化搜索用 ,dp[i][j][k]表示考慮i位數字,支點為j,力矩和為k int digit[20]; long long DFS(int pos,int central,int pre,int limit){ //pos表示當前位置,central表示支點,pre表示從最高位到pos的力矩之和,limit表示是否有上限 1有 0無 if(pos<=0) //已經全部組合 return pre==0; if(pre<0) //前面組合而成的力矩之和已經小於0,后面的也都是負數 return 0; if(!limit && dp[pos][central][pre]!=-1) //沒有上限且當前的狀態之前已經搜索過 return dp[pos][central][pre]; int end=limit?digit[pos]:9; //有上限就設為上限,否則最高到9 long long ans=0; for(int i=0;i<=end;i++) ans+=DFS(pos-1,central,pre+i*(pos-central),limit && (i==end)); if(!limit) dp[pos][central][pre]=ans; return ans; } long long Solve(long long x){ int len=0; while(x){ digit[++len]=x%10; x/=10; } digit[len+1]=0; long long ans=0; for(int i=1;i<=len;i++) //枚舉支點 ans+=DFS(len,i,0,1); return ans-(len-1); //除掉全0的情況,00,0000滿足條件,但是重復了 } int main(){ //freopen("input.txt","r",stdin); long long a,b; int t; scanf("%d",&t); memset(dp,-1,sizeof(dp)); while(t--){ scanf("%I64d%I64d",&a,&b); printf("%I64d\n",Solve(b)-Solve(a-1)); } return 0; }
7, HDU 3709 SNIBB : http://acm.hdu.edu.cn/showproblem.php?pid=3271
題意:將一個數轉化成B進制后,他的val表示的是各位上的數字和。
首先還是預處理,dp[i][j]表示轉化成B進制后,長度為i的數中,數字和為j的數字有多少個,感覺越來越像數位DP。。。
對於詢問1:壓根就是數位DP,從高位開始枚舉,記錄之前已經出現的位數和,然后枚舉當前位。注意區間的開閉問題,邊界處理好
對於詢問2:首先通過詢問1得出的數目,判斷是否存在第K大,然后就是二分答案,判斷[l,mid]中和為m的數有多少個。

#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int dp[32][310]; void Init(int b,int m){ //轉換成B進制后,長度為i的數中各位和為j的個數 memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1;i<32;i++) for(int j=0;j<=m;j++) for(int k=0;k<b && k+j<=m;k++) dp[i][j+k]+=dp[i-1][j]; } int Cal(int n,int b,int m){ //統計[0,n]中轉換成b進制,和為m的個數 int digit[35],len=0; while(n){ digit[++len]=n%b; n/=b; } digit[len+1]=0; int ans=0,tot=0; for(int i=len;i>0;i--){ for(int j=0;j<digit[i] && m-tot-j>=0;j++) ans+=dp[i-1][m-tot-j]; tot+=digit[i]; if(tot>m) break; } if(tot==m) //本身的和就是m,注意別落下 ans++; return ans; } int main(){ //freopen("input.txt","r",stdin); int cases=0; int op,x,y,b,m,k; while(~scanf("%d%d%d%d%d",&op,&x,&y,&b,&m)){ Init(b,m); if(x>y) swap(x,y); printf("Case %d:\n",++cases); int ans=Cal(y,b,m)-Cal(x-1,b,m); if(op==1){ printf("%d\n",ans); continue; } scanf("%d",&k); if(k>ans){ puts("Could not find the Number!"); continue; } int low=x,high=y,mid; while(low<high){ //二分答案,判斷在[l,mid]中和為m的個數 mid=(int)((((long long)low+(long long)high))/2); int now=Cal(mid,b,m)-Cal(x-1,b,m); if(now<k) low=mid+1; else high=mid; } printf("%d\n",low); } return 0; }