URAL 1057 Amount of Degrees 数位dp

原题链接:URAL 1057 Amount of Degrees

做了前两道数位dp,作者就在想:“数位dp原来这么简单啊,连状态都是一个模式,都是dp[i][j]表示以j开头的i位数字,看来再刷一道题就可以完全学会数位dp了。”

可是这道题目现场打脸,orz,一点思路都没有,用前两道类似的状态根本无法解决此题。直到看了大神的论文才知道数位dp的水原来这么深(可能只是本渣觉得深),以后还是老老实实刷题吧。

论文链接:算法合集之《浅谈数位类统计问题》

相信看了一遍之后就可以明白大概的意思了。不过读者可能对一个地方还是心存疑惑,那就是非二进制怎么处理?为什么论文中的方法是正确的呢?

先看下面这幅图

URAL 1057 Amount of Degrees 数位dp_第1张图片

这是三进制的情况下的完全三叉树,显而易见,节点数字>1的都是不合理的,因为最后答案用3进制表示肯定都是0和1。

当我们把不合理的树“剪”了之后,发现剩下的树就是完全二叉树!

回顾一下论文中的处理方式:从高位到低位查找,碰到第一个非0、1的数字时,将该位数字及其右边的所有数字全部变成1,然后按照二进制的方法来处理。

比如三进制表示的数字021,按照上述方法就变成了011。可以看得出来这是最贴近021的合法数字,这样处理之后保证了符合条件的数字都小于等于011,所以这种处理是正确的。其实这么说可能还是有点抽象,仔细的看图理解一下说不定更容易懂。

代码如下:

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long int ll;
int dp[35][35];
ll X, Y, K, B;

void init()
{
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for (int i = 1; i <= 31; i++)
    {
        dp[i][0] = 1;
        for (int j = 1; j <= i; j++)
            dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
    }
}

// 计算[0, n)中有多少个二进制含有K个1的数字
int cal(ll x)
{
    int ans = 0, tot = 0;
    for (int i = 31; i>= 1; i--)
    {
        if (x & (1 << i))
        {
            tot++;
            if (tot > K)
                break;
            x ^= (1 << i);
        }
        if ((1 << (i - 1)) <= x)
            ans += dp[i - 1][K - tot];
    }
    return ans;
}

// 将b进制的情况转化成2进制
ll change(ll x)
{
    int digit[35] = {0}, len = 0;
    while (x)
    {
        digit[++len] = x % B;
        x /= B;
    }
    int high = len;
    while (high >= 1 && digit[high] <= 1)
        high--;
    for (int i = high; i >= 1; i--)
        digit[i] = 1;
    ll res = 0;
    for (int i = len; i >= 1; i--)
        res = res * 2 + digit[i];
    return res;
}

int main()
{
    //freopen("test.txt", "r", stdin);

    init();
    while (~scanf("%lld%lld%lld%lld", &X, &Y, &K, &B))
    {
        ll x = change(X);
        ll y = change(Y);
        printf("%d\n", cal(y + 1) - cal(x));
    }
    return 0;
}


你可能感兴趣的:(ACM-数位dp)