您的位置:首页 > 其它

URAL 1057 Amount of Degrees 数位dp

2017-06-03 17:12 302 查看
原题链接:URAL 1057 Amount of Degrees

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

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

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

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

先看下面这幅图



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

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

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

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

代码如下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息