數位dp入門題目總結——區間中滿足要求的數的個數

引言

在算法競賽中,有一類求出給定區間中符合要求的數的個數問題,這類問題往往區間範圍較大,無法通過枚舉區間中數再判斷條件這種方式來求解,數位dp就是一種解決這種方式的策略。

給出一篇寫的很好地文章鏈接

總體策略

若區間符合可加減,
求解[l,r] 滿足條件的數個數可以通過[0,r][0,l1] 來完成

問題轉化爲求解[0,n]
對於一個小於n的數,必然從高位到低位有一位數小於n中對應位置上的數,這樣我們在循環時可以通過枚舉每一位數上的數字範圍來求解。

不要62

題目鏈接

杭州人稱那些傻乎乎粘嗒嗒的人爲62(音:laoer)。
杭州交通管理局經常會擴充一些的士車牌照,新近出來一個好消息,以後上牌照,不再含有不吉利的數字了,這樣一來,就可以消除個別的士司機和乘客的心理障礙,更安全地服務大衆。
不吉利的數字爲所有含有4或62的號碼。例如: 62315 73418 88914
都屬於不吉利號碼。但是,61152雖然含有6和2,但不是62連號,所以不屬於不吉利數字之列。
你的任務是,對於每次給出的一個牌照區間號,推斷出交管局今次又要實際上給多少輛新的士車上牌照了。

思路:
定義dp[i][j] 表示以j開頭位數爲i的滿足條件的數的個數,
初始化dp[0][0] =1, 接着預處理完成賦值,包含前導0。

    dp[0][0] = 1;
    for(i=1; i<=7; i++)     //循環多少位數 
    {
        for(j=0; j<=9; j++)     //循環第i位數字取值 
        {
            for(k=0; k<=9; k++)     //循環第i-1位數字取值 
            {
                if(j!=4 && !(j==6 && k==2))
                {
                    dp[i][j] += dp[i-1][k];
                }
            }
        }
    }

遞推時由低位向高位枚舉,逐步求出所有dp值

通過區間相減方式區間個數問題變爲[0, x]間個數問題,統計數字前用v數組記錄x每一位數字值。

for(i=size; i>0; i--)
    {
        for(j=v[i-1]-1; j>=0; j--)
        {
            if(j!=4 && !(j==2 && v[i] == 6)) 
            {
                ans += dp[i][j];
            }
        }

        if(v[i-1] == 4 || (v[i] == 6 && v[i-1] == 2))   //第i位取原數字,進行下一位時判斷這一位是否可行 
        {
            break;
        }
    }

統計數時由高位向低位枚舉,注意邊界時(即該位置取v相同數字時)取到4或和之前一位構成62時及時break。
調用時要調用solve(x+1).

程序代碼:

#include<iostream>
#include<vector>
using namespace std;
int dp[8][10];   //dp[i][j]  以j開頭位數爲i的滿足條件的數的個數

int work(int x)
{
    vector<int> v;      //x各個數位上的數字 
    while(x > 0){
        v.push_back(x % 10);
        x /= 10;
    }

    int ans = 0;
    int i, j;

    int size = v.size();
    v.push_back(0);
    for(i=size; i>0; i--)
    {
        for(j=v[i-1]-1; j>=0; j--)
        {
            if(j!=4 && !(j==2 && v[i] == 6)) 
            {
                ans += dp[i][j];
            }
        }

        if(v[i-1] == 4 || (v[i] == 6 && v[i-1] == 2))   //第i位取原數字,進行下一位時判斷這一位是否可行 
        {
            break;
        }
    }

    return ans;
}

int main()
{
    //freopen("tt.in", "r", stdin);
    //freopen("b", "w", stdout);
    int n, m;
    int i, j, k;

    dp[0][0] = 1;
    for(i=1; i<=7; i++)     //循環多少位數 
    {
        for(j=0; j<=9; j++)     //循環第i位數字取值 
        {
            for(k=0; k<=9; k++)     //循環第i-1位數字取值 
            {
                if(j!=4 && !(j==6 && k==2))
                {
                    dp[i][j] += dp[i-1][k];
                }
            }
        }
    }

    while(1)
    {
        cin >> n >> m;
        if(n == 0 && m == 0){
            break;
        }   

        int ansa = work(n);
        int ansb = work(m+1);

        cout << ansb - ansa << endl;

    }   

    return 0;
}

Bomb

題目鏈接

The counter-terrorists found a time bomb in the dust. But this time
the terrorists improve on the time bomb. The number sequence of the
time bomb counts from 1 to N. If the current number sequence includes
the sub-sequence “49”, the power of the blast would add one point. Now
the counter-terrorist knows the number N. They want to know the final
points of the power. Can you help them?

題意:
求給定區間中含有49的數字個數

思路1:
反向處理,參考上題求出不含有49的個數,再用區間個數相減。

思路2:
正向思維,定義
dp[i][j][0] 表示以j開頭i位數中不含49的個數
dp[i][j][1] 表示以j開頭i位數中含49的個數

若當前位和後一位不構成49,dp[i][j][0]=9k=0dp[i1][k][0]dp[i][j][1]=9k=0dp[i1][k][1]
若當前位和後一位 構成49,dp[i][j][1]=9k=0(dp[i1][k][0]+dp[i1][k][1])

統計數字時同樣要注意邊界條件,若之前邊界出現49,則後面數字無論出沒出現49都要累加。

程序代碼:

#include<iostream>
#include<vector>
using namespace std;
#define LL long long
LL dp[65][10][2];   //dp[i][j][0]表示以j開頭i位數中不含49的個數 
                    //dp[i][j][1]表示以j開頭i位數中含49的個數 

LL solve(LL x)
{
    vector<LL> v;
    while(x > 0)
    {
        v.push_back(x % 10);
        x /= 10;    
    }   

    int size = v.size();
    v.push_back(0);
    LL ans = 0;
    int i, j;
    bool pd = false;
    for(i=size; i>0; i--)
    {
        for(j=0; j<v[i-1]; j++)
        {
            if(v[i] == 4 && j == 9 || pd)
            {
                ans += dp[i][j][0] + dp[i][j][1];
            }
            else{
                ans += dp[i][j][1];
            }
        }

        if(v[i] == 4 && v[i-1] == 9)    //若i位和i-1位分別爲4和9,表示後面找到的位數都滿足要求 
        {
            pd = true;
        }
    }

    return ans;
}

int main()
{
    //freopen("tt", "r", stdin); 
    //freopen("a", "w", stdout);
    int T;
    int i, j, k;
    LL n;
    cin >> T;

    dp[0][0][0] = 1;
    dp[0][0][1] = 0;
    for(i=1; i<=63; i++)
    {
        for(j=0; j<=9; j++)     //第i位 
        {
            for(k=0; k<=9; k++)     //第i-1位 
            {
                if(!(j==4 && k==9))
                {
                    dp[i][j][0] += dp[i-1][k][0];   
                    dp[i][j][1] += dp[i-1][k][1];
                }   

                else{
                    dp[i][j][1] += dp[i-1][k][0] + dp[i-1][k][1];
                }
            }       
        }
    }

    while(T--)
    {
        cin >> n;
        //cout << n << " ";
        cout << solve(n+1) << endl;
    }

    return 0;
}

B-number

題目鏈接

A wqb-number, or B-number for short, is a non-negative integer whose
decimal form contains the sub- string “13” and can be divided by 13.
For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not.
Your task is to calculate how many wqb-numbers from 1 to n for a given
integer n.

題意:
含有13且能被13整除的數個數

思路:
此題用記憶化搜索較爲容易,網上流傳較廣的記憶化數位dp模板如下

    int dfs(int i, int s, bool e) {  
        if (i==-1) return s==target_s;  
        if (!e && ~f[i][s]) return f[i][s];  
        int res = 0;  
        int u = e?num[i]:9;  
        for (int d = first?1:0; d <= u; ++d)  
            res += dfs(i-1, new_s(s, d), e&&d==u);  
        return e?res:f[i][s]=res;  
    }  

i表示當前i位置
s表示當前狀態
e表示是否是當前前綴(即後面的數字能否任意填)
只有在數字可以任意填時,才記錄f數組值

應用在這題上,定義
dp[i][j][mod][has]表示第i個位置 以j開頭 和13餘數爲mod 是否含有13的數個數

程序代碼:

#include<iostream>
#include<cstring>
#include<vector> 
using namespace std;
int dp[11][10][13][2];  //dp[i][j][mod][has]:第i個位置 以j開頭 和13餘數爲mod 是否含有13的數個數 
vector<int> v;

int dfs(int pos, int k, int mod, int has, bool limit)
{
    if(pos == 0){
        return mod == 0 && has; 
    }   

    if(!limit && dp[pos][k][mod][has] != -1)
    {
        return dp[pos][k][mod][has];
    }

    int num = limit? v[pos-1]: 9;
    int i;
    int ans = 0;
    for(i=0; i<=num; i++)
    {
        if(k==1 && i==3){
            ans += dfs(pos-1, i, (mod*10+i)%13, 1, limit&&i==num);
        }
        else{
            ans += dfs(pos-1, i, (mod*10+i)%13, has, limit&&i==num);
        }
    }

    if(!limit){
        dp[pos][k][mod][has] = ans;
    }
    return ans;
}

int solve(int x)
{
    v.clear();
    while(x){
        v.push_back(x % 10);
        x /= 10;
    }

    return dfs(v.size(), 0, 0, 0, true);
}

int main()
{
    int n;
    int i, j;
    memset(dp, -1, sizeof(dp));
    while(cin >> n)
    {
        //memset(dp, -1, sizeof(dp));
        cout << solve(n) << endl;
    }

    return 0;
 } 

Balanced Number

題目鏈接

A balanced number is a non-negative integer that can be balanced if a
pivot is placed at some digit. More specifically, imagine each digit
as a box with weight indicated by the digit. When a pivot is placed at
some digit of the number, the distance from a digit to the pivot is
the offset between it and the pivot. Then the torques of left part and
right part can be calculated. It is balanced if they are the same. A
balanced number must be balanced with the pivot at some of its digits.
For example, 4139 is a balanced number with pivot fixed at 3. The
torqueses are 4*2 + 1*1 = 9 and 9*1 = 9, for left part and right part,
respectively. It’s your job to calculate the number of balanced
numbers in a given range [x, y].

題意:
找出區間內平衡數的個數,所謂的平衡數,就是以這個數字的某一位爲支點,另外兩邊的數字大小乘以力矩之和相等,即爲平衡數
4139 選擇3爲支點. 4*2 + 1*1 = 9 and 9*1 = 9 ,即爲平衡數

思路:
定義dp[i][k][s] 表示記錄以k爲支點 正在填第i位數字時 力矩和爲s的數字個數

程序代碼:

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
#define LL long long 
LL dp[20][20][4000];
vector<int> v;

LL dfs(int pos, int k, int s, bool isuse)   //pos位置  k支點 s力矩和 
{
    if(pos == 0){
        return s==0;
    }   
    if(s < 0){
        return 0;
    }
    if(!isuse && dp[pos][k][s] != -1){
        return dp[pos][k][s];
    }

    int i;
    LL ans = 0;
    int choice = isuse? v[pos-1]:9;
    for(i=0; i<=choice; i++)
    {
        int news = s + (pos-k) * i;
        ans += dfs(pos-1, k, news, isuse && i==choice);
    }

    if(!isuse){
        dp[pos][k][s] = ans;
    }
    return ans;
}

LL solve(LL x)
{
    v.clear();
    while(x)//注意填x>0的話-1不滿足,所以填x
    {
        v.push_back(x % 10);
        x /= 10;    
    }   

    int i;
    LL ans = 0;
    for(i=1; i<=v.size(); i++)
    {
        ans += dfs(v.size(), i, 0, true);
    }

    return ans - (v.size()-1);
}

int main()
{
    int T;
    freopen("tt", "r", stdin);
    freopen("a", "w", stdout); 
    int i, j;
    cin >> T;
    memset(dp, -1, sizeof(dp));
    while(T--)
    {
        LL x, y;
        cin >> x >> y;
        cout << solve(y) - solve(x - 1) << endl;
    }

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