數位DP 不斷學習中。。。。


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;
}
View Code

另附一種暴力預處理法:

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;
}
View Code

 

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

 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;
}
View Code

 

另附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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM