副本1——dp-URAL - 1057 (数位dp)

URAL - 1057

看的别人的思路,很清晰;点击打开链接

题目大意: 求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K 个互不相等的 B 的整 数次幂之和。例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意: 17 = 24+20, 18 = 24+21, 20 = 24+22。 输入:第一行包含两个整数X 和 Y。接下来两行包含整数 K 和 B。 输出:只包含一个整数,表示满足条件的数的个数。 数据规模:1 ≤ X ≤ Y ≤ 231−1,1 ≤ K ≤ 20, 2 ≤ B ≤ 10。 分析: 所求的数为互不相等的幂之和,亦即其B 进制表示的各位数字都只能是 0和 1。因此, 我们只需讨论二进制的情况,其他进制都可以转化为二进制求解。 很显然,数据范围较大,不可能采用枚举法,算法复杂度必须是 log(n)级别,因此我们 要从数位上下手。
 浅谈数位类问题 刘聪
第 2页,共 12 页
本题区间满足区间减法,因此可以进一步简化问题:令 count[i..j]表示[i..j]区间内合法数 的个数,则count[i..j]=count[0..j]-count[0..i-1]。换句话说,给定 n,我们只需求出从0 到 n 有多少个符合条件的数。 假设 n=13,其二进制表示为 1101,K=3。我们的目标是求出 0 到 13 中二进制表示含 3 个 1 的数的个数。为了方便思考,让我们画出一棵高度为 4 的完全二叉树:

为了方便起见,树的根用 0 表示。这样,这棵高度为4 的完全二叉树就可以表示所有 4 位二进制数(0..24-1) ,每一个叶子节点代表一个数。其中,红色路径表示 n。所有小于 n的 数组成了三棵子树,分别用蓝色、绿色、紫色表示。因此,统计小于 13 的数,就只需统计 这三棵完整的完全二叉树:统计蓝子树内含 3 个 1 的数的个数、统计绿子树内含 2 个1 的数 的个数(因为从根到此处的路径上已经有 1 个 1),以及统计紫子树内含 1个 1 的数的个数。 注意到,只要是高度相同的子树统计结果一定相同。而需要统计的子树都是“右转”时遇到 的。当然,我们不能忘记统计n 本身。实际上,在算法最初时将 n 自加 1,可以避免讨论 n 本身,但是需要注意防止上溢。 剩下的问题就是,如何统计一棵高度为 i的完全二叉树内二进制表示中恰好含有 j 个1 的数的个数。这很容易用递推求出:设 f[i,j]表示所求,则分别统计左右子树内符合条件数的 个数,有 f[i,j]=f[i-1,j]+f[i-1,j-1]。 这样,我们就得出了询问的算法:首先预处理 f,然后对于输入 n,我们在假想的完全 二叉树中,从根走到 n所在的叶子,每次向右转时统计左子树内数的个数。 下面是 C++代码:

void init()
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=31;i++)
    {
        f[i][0]=f[i-1][0];
        for(int j=1;j<=i;j++)
        {
             f[i][j]=f[i-1][j]+f[i-1][j-1];
        }
    }
}
int getans(int x,int k)
{
    int tot=0;//当前1数目
    int ans=0;
    for(int i=31;i>0;i--)
    {
        if(x&((long long)1<<i))
        {
             tot++;
             if(tot>k)//如果已经取到所有k的情况,则结束
                break;
             x=x^((long long)1<<i);
        }
        if((1<<(i-1))<=x)
            ans+=f[i-1][k-tot];
    }
    if(tot+x==k)  //因为i不取到0 所以对于x最低位为1或0的情况进行特判增加
          ans++;
    return ans;
}

最后的问题就是如何处理非二进制。对于询问 n,我们需要求出不超过 n 的最大 B 进制 表示只含 0、1的数:找到n 的左起第一位非 0、1 的数位,将它变为 1,并将右面所有数位 设为 1。将得到的 B 进制表示视为二进制进行询问即可。 预处理递推 f的时间复杂度为O(log2(n)),共有 O(log(n))次查询,因此总时间复杂度为 O(log2(n))。 实际上,最终的代码并不涉及树的操作,我们只是利用图形的方式来方便思考。因此也 可以只从数位的角度考虑:对于询问 n,我们找到一个等于 1 的数位,将它赋为 0,则它右 面的数位可以任意取,我们需要统计其中恰好含有 K-tot 个 1 的数的个数(其中 tot 表示这 一位左边的1 的个数) ,则可以利用组合数公式求解。逐位枚举所有”1”进行统计即可。 我们发现,之前推出的 f 正是组合数。同样是采用“逐位确定”的方法,两种方法异曲 同工。当你觉得单纯从数位的角度较难思考时,不妨画出图形以方便思考。


AC代码

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>


using namespace std;
const long long maxx=((long long )1<<31)-1;
int f[50][50];
int num[50];
void init()
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=31;i++)
    {
        f[i][0]=f[i-1][0];
        for(int j=1;j<=i;j++)
        {
             f[i][j]=f[i-1][j]+f[i-1][j-1];
        }

    }

}
int get(long long y,int b)
{
    int cnt=0;
    int nowy=0;
    while(y>0)
    {
        num[cnt++]=y%b;
        y/=b;
    }
    reverse(num,num+cnt);
    int chant=cnt;
    for(int i=0;i<cnt;i++)
    {
      //  cout<<num[i];
        if(num[i]>1)
        {
            chant=i;
            break;
        }
    }
   // cout<<endl;
    for(int i=0;i<cnt;i++)
    {
        if(i<chant)
          nowy=nowy*2+num[i];
        else
            nowy=nowy*2+1;
    }
  //  cout<<nowy<<endl;
    return nowy;
}
int getans(int x,int k)
{
    int tot=0;//当前1数目
    int ans=0;
    for(int i=31;i>0;i--)
    {
        if(x&((long long)1<<i))
        {
             tot++;
             if(tot>k)//如果已经取到所有k的情况,则结束
                break;
             x=x^((long long)1<<i);
        }
        if((1<<(i-1))<=x)
            ans+=f[i-1][k-tot];
    }
    if(tot+x==k)  //因为i不取到0 所以对于x最低位为1或0的情况进行特判增加
          ans++;
    return ans;
}


int main()
{
    init();
    long long x,y;
    long long k,b;
    cin>>x>>y;
    cin>>k>>b;
    int nowy=get(y,b);
    int nowx=get(x-1,b);
    int ans=getans(nowy,k)-getans(nowx,k);
    //cout<<getans(nowy,k)<<endl;
    //cout<<getans(nowx,k)<<endl;
    cout<<ans<<endl;


    return 0;
}



猜你喜欢

转载自blog.csdn.net/sinat_36215255/article/details/80231423
今日推荐