数位dp进阶题目——较复杂的状态刻画

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27008079/article/details/60584376

前言

在前一节链接里,我们讨论了数位dp的基础应用,从数位dp的简单状态刻画状态方程的给出,以及之后给出了较为统一的记忆化模板来解决大多数问题,在这一节里首先给出一个较复杂的状态方程求解问题,剩下3个用记忆化解决的问题来加深印象。

The Counting Problem

POJ 2282 题目链接
Given two integers a and b, we write the numbers between a and b, inclusive, in a list. Your task is to calculate the number of occurrences of each digit. For example, if a = 1024 and b = 1032, the list will be
1024 1025 1026 1027 1028 1029 1030 1031 1032

there are ten 0’s in the list, ten 1’s, seven 2’s, three 3’s, and etc.

【题目大意】求出给定区间内0~9每个数出现的次数

【题目思路】定义dp[i][j][k]为 以j开头含有i位数字 其中出现数字k的个数
状态转移方程:若j=k,(即第一位数字为出现的数字), dp[i][j][k]=10i1 ,表示以j开头后面任意;
dp[i][j][k]=9kk=0dp[i1][kk][k] ,从低位向高位递推

需要注意:

  1. 在统计个数时,对出现在前缀上的数字需要加上之后组成的数字个数。
  2. 第一位取0时要特殊考虑,要减去这次0出现的次数以及后面每次0在首位出现的次数。

【程序代码】

/*求出区间内0~9的个数
*/
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int dp[10][10][10]; //dp[i][j][k] 表示以j开头i位数字 含有数字k的个数 
int ans[10][2];
int pos = 0;
vector<int> v;

int work(int n)  //求10^n 
{
    int s = 1, i;
    for(i=1; i<=n; i++)
    {
        s *= 10;        
    }
    return s;
}

int js(int j)   //求v[j-1]~v[0]组成的数字 
{
    int s = 0;
    int i;
    for(i=j; i>=0; i--)
    {
        s = s* 10 + v[i];   
    }   

    return s;
}

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

    int i, j, k;
    for(i=v.size(); i>0; i--)
    {
        for(j=v[i-1]-1; j>=0; j--)
        {
            for(k=0; k<=9; k++)
            {
                ans[k][pos] += dp[i][j][k];

                if(j==0 && i==v.size() && k==0) //第一位数字取0减去这次0在第一位出现的个数
                                                //以及后面每次0在首位的情况。 
                {
                    int t = work(v.size()-1);
                    while(t > 1){
                        ans[k][pos] -= t;
                        t /= 10;
                    }

                }
            }
        }
        ans[v[i-1]][pos] += js(i-2);    //第i位数字取原数字需要加 后面组成的数字个数 
    }
    pos++;
}

int main()
{
    int a, b;
    int i, j, k, kk;

    for(i=0; i<=9; i++)
    {
        dp[1][i][i] = 1;
    }

    for(i=2; i<=9; i++)
    {
        for(j=0; j<=9; j++)
        {
            for(k=0; k<=9; k++)
            {
                if(j==k){
                    dp[i][j][k] = work(i-1);
                }
                for(kk=0; kk<=9; kk++)
                {
                    dp[i][j][k] += dp[i-1][kk][k];  
                }   
            }   
        }   
    }   

    while(1)
    {
        cin >> a >> b;
        pos = 0;
        memset(ans, 0, sizeof(ans));
        if(a == 0 && b == 0){
            break;
        }
        if(a > b){
            int t = a;
            a = b;
            b = t;
        }

        solve(b+1);
        solve(a);

        for(i=0; i<=9; i++)
        {
            cout << ans[i][0] - ans[i][1] << " ";
        }
        cout << endl;
    }
    return 0;
}

Round Numbers

POJ3252 题目链接
The cows, as you know, have no fingers or thumbs and thus are unable to play Scissors, Paper, Stone’ (also known as ‘Rock, Paper, Scissors’, ‘Ro, Sham, Bo’, and a host of other names) in order to make arbitrary decisions such as who gets to be milked first. They can’t even flip a coin because it’s so hard to toss using hooves.

They have thus resorted to “round number” matching. The first cow picks an integer less than two billion. The second cow does the same. If the numbers are both “round numbers”, the first cow wins,
otherwise the second cow wins.

A positive integer N is said to be a “round number” if the binary representation of N has as many or more zeroes than it has ones. For example, the integer 9, when written in binary form, is 1001. 1001 has two zeroes and two ones; thus, 9 is a round number. The integer 26 is 11010 in binary; since it has two zeroes and three ones, it is not a round number.

Obviously, it takes cows a while to convert numbers to binary, so the winner takes a while to determine. Bessie wants to cheat and thinks she can do that if she knows how many “round numbers” are in a given range.

Help her by writing a program that tells how many round numbers appear in the inclusive range given by the input (1 ≤ Start < Finish ≤ 2,000,000,000).

【题目大意】统计给定区间内 数字的二进制表示中0个数大于1个数,这样符合条件的数据有多少。

【题目思路】运用记忆化搜索,定义dp[i][j][k] 处理i位置 j代表(0个数-1个数) k=1表示之前有填过1
为了防止j出现负数,将j累加40(初始传递值为40,以40定义标准1个数等同0个数),dfs中用has记录是否之前有1,若没有,则不记录这次01个数差,因为数字不能出现前导0。

【程序代码】

#include<iostream>
#include<vector>
#include<stdio.h>
#include<cstring>
using namespace std;

int dp[50][80][2];  //dp[i][j][k]处理i位置  j代表0个数-1个数 k=1表示之前有填过1 
vector<int> v;
int dfs(int pos, int s, int has, bool limit)
{
    if(pos == 0){
        return s >= 40&&has;
    }   
    if(!limit && dp[pos][s][has] != -1)
    {
        return dp[pos][s][has];
    }

    int i;
    int num = limit? v[pos-1]: 1;
    int ans = 0;
    for(i=0; i<=num; i++)
    {

        if(has){
            if(i==0){
                ans += dfs(pos-1, s+1, 1, limit&&i==num);
            }
            else{
                ans += dfs(pos-1, s-1, 1, limit&&i==num);
            }
        }

        else{
            if(i==1){
                ans += dfs(pos-1, s-1, 1, limit&&i==num);
            }
            else{
                ans += dfs(pos-1, s, has, limit&&i==num);
            }
        }
    }

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

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

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

int main()
{
    int x, y;
    //freopen("tt", "r", stdin);
    //freopen("a", "w", stdout);
    memset(dp, -1, sizeof(dp));
    while(~scanf("%d%d",&x,&y))
    printf("%d\n",solve(y)-solve(x-1));  
    return 0;
 } 

F(x)

hdu4734 题目链接
For a decimal number x with n digits (AnAn-1An-2 … A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there between 0 and B, inclusive, whose weight is no more than F(A).

【题目大意】
F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1,给出A,B,求出[0, B]中x个数,满足F(x) < F(A)

【题目思路】
记忆化。
A范围[0, 10^9],F(A)范围[0, 9*2^9],调用dfs时权值和赋值F(A)
定义dp[i][j] 表示正在处理第i位,权值和剩下j时的方法总数,若最终处理0位时,权值和小于0,表示此种填充方式可行。
剩下的套记忆化的模板即可

【程序代码】

#include<iostream>
#include<stdio.h>
#include<vector>
#include<cstring>
using namespace std;
int dp[11][15000];
vector<int> v;

int work(int n) //求2^n
{
    int s = 1;
    for(int i=1; i<=n; i++)
    {
        s *= 2; 
    }   
    return s;
}

int dfs(int pos, int w, bool limit)
{
    if(pos == 0){
        return 1;
    }

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

    int num = limit? v[pos-1]: 9;
    int i;
    int ans = 0;
    for(i=0; i<=num; i++)
    {
        int h = i * work(pos-1);
        if(h <= w)
        {
            ans += dfs(pos-1, w-h, limit&&i==num);
        }
    }

    if(!limit){
        dp[pos][w] = ans;
    }
    return ans;
}

int solve(int A, int B)
{
    v.clear();
    while(B > 0){
        v.push_back(B % 10);
        B /= 10;
    }

    int i = 1;
    int s = 0;  //求F(A)
    while(A > 0)
    {
        int t = A % 10;
        s += t * i;
        i *= 2;
        A /= 10;
    }

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

int main()
{
    int t;
    int i, j, k;
    cin >> t;
    memset(dp, -1, sizeof(dp));
    for(i=1; i<=t; i++)
    {
        int A, B;
        scanf("%d%d", &A, &B);

        int ans = solve(A, B);
        printf("Case #%d: %d\n", i, ans);
    }

    return 0;
}

X mod f(x)

hdu4389 题目链接
Here is a function f(x):
   int f ( int x ) {
    if ( x == 0 ) return 0;
    return f ( x / 10 ) + x % 10;
   }

   Now, you want to know, in a given interval [A, B] (1 <= A <= B <= 109), how many integer x that mod f(x) equal to 0.

【题目大意】
求给定区间内 数字x % x各位上数字和=0,这样的x有多少个。

【题目思路】
前一节有一道题是%13的问题,这次是% x各位上数字和,x各位上数字和最大为9*9=81,这次通过枚举1~81来调用dfs
定义dp[i][j][k][m] 还剩i位数 前面的数字和为j 目前组成的数与m相余为k 的方法数,剩下的套模板和hdu3652 差不多。

【程序代码】

#include<iostream>
#include<stdio.h>
#include<vector>
#include<cstring>
using namespace std;
vector<int> v;
int dp[11][82][82][82];//dp[i][j][k][m] : 还剩i位数  前面的数字和为j 目前组成的数与m相余为k 

int dfs(int pos, int sum, int modv, int mod, bool limit)
{
    if(pos == 0){
        if(sum == mod && modv == 0){
            return 1;
        }
        return 0;
    }   

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

    int num = limit? v[pos-1]:9;
    int i, j;
    int ans = 0;
    for(i=0; i<=num; i++)
    {
        ans += dfs(pos-1, sum+i, (modv*10+i) % mod, mod, limit&&i==num);
    }

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

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

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

int main()
{
    int t;
//  freopen("tt", "r", stdin);
//  freopen("a", "w", stdout);
    int i, j, k;
    cin >> t;
    memset(dp, -1, sizeof(dp));
    for(i=1; i<=t; i++)
    {
        int x, y;
        cin >> x >> y;
        printf("Case %d: %d\n", i, solve(y) - solve(x-1));
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_27008079/article/details/60584376