• 【算法•日更•第十二期】信息奥赛一本通1585:【例 1】Amount of Degrees题解


      废话不多说,直接上题:


    1585: 【例 1】Amount of Degrees


    时间限制: 1000 ms         内存限制: 524288 KB
    提交数: 130     通过数: 68 

    【题目描述】

    原题来自:NEERC 2000 Central Subregional,题面详见 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。

    【输出】

    只包含一个整数,表示满足条件的数的个数。

    【输入样例】

    15 20
    2
    2

    【输出样例】

    3

    【提示】

    数据范围与提示:

    对于全部数据,1XY2311,1K20,2B10

    【来源】


      这道题一看就会先想到暴力||搜索算法,但是,它们的时间复杂度还是太高,拿不了满分。仔细一看数据规模,就会发现数据规模实在太大了,于是我们就不能设计O(n2)或更高时间复杂度的算法了,甚至O(n)都存在问题,无法满足,太血腥了,我们不得不设计一个O(log n)的算法。

      但是这么快的算法绝对不可能是拿来数据直接算的,那么就要采用预处理的方法,减小时间复杂度。

      在这之前,先来思考,O(log n)听着这么洋气,那么什么东西和这有关呢?对,完全二叉树的层数!没错,我们虽然不需要用到树的数据结构,但是也要在脑海中想象一棵完全二叉树。算了,不用想象了,直接上图:

      先吐槽一下,一本通提高篇上的图怎么没有颜色,哪来什么蓝、绿、紫子树。

      

      这才是正宗的图:

      题目中只告诉了B进制,那么我们可以先假设是二进制,那么就可以画出一棵如上图所示的深度为4的完全二叉树。假设当前要求0~13区间内k=3,b=2满足要求的数的个数,13的二进制表示是1101。为了方便,根节点就为0表示;

      没错,图中红线所穿过的节点正是13,那么0~13区间就分为了图中的蓝子树、绿子树、紫子树和13四个部分;细细观察就会发现每一棵带着其他颜色的子树都是13这条路径上节点的左子树。

      很明显,所有满足条件的数的路径上都不多不少有3个“1”,其实1不仅是这些数二进制中的组成部分,更是这个位置上的次方选还是不选的标志,1是选,0是不选,如果不懂,可以亲手画图试试就知道了;

      那么我们不妨用f[i][j]来表示深度为i的完全二叉树中选j个“1”的符合条件的数的个数。那么则有f[i][j]=f[i-1][j]+f[i-1][j-1],为什么呢?实在不好理解,那么就直接上个图吧:

      

      这样就清晰多了,红线下的是第i层,红线上的就是第i-1层,那么我们会发现i-1层选j个的数都会和第i层的0相接,形成f[i-1][j]个满足条件的数;反之i-1层选j-1个数的都会和i层的1相接,形成f[i-1][j-1]个满足条件的数,那么f[i][j]自然就是它们的和了。

      这就是预处理。

      很明显,[x,y]区间内的满足条件的数的个数可以表示为[0,y]区间满足条件个数-[0,x-1]区间满足条件个数(类似于前缀和)。

      那么我们在计算[0,y]区间满足条件个数和[0,x-1]区间满足条件个数时,只要一直在向右走时(这一位是1)把左子树满足条件个数记录上,最后再判断一下当前路径是否满足条件即可。

      如果你看过刘聪的浅谈数位(戳这里了解),那么你一定会认为他讲的很详细。

      但是结尾处有一句话:对于询问n,我们需要求出不超过n的最大B进制数,表示只含0、1的数:找到n的左起第一位非0、1的数位,将它变为1,并将它右面所有数位设成1。将得到的B进制数表示视为二进制进行询问即可。(出自刘聪的浅谈数位&信息奥赛一本通提高篇)

      这句话只写作法,不写原因,使小编很长时间不能理解。

      其实很好理解,比方说13,我们将它变成2进制的形式,那么就是(1101)2,也可以表示为这样的形式:13=1*23+1*22+0*21+1*20,那么对于一个数n,就可以这样表示:n = ai*n+ ai-1*ni-1 + ai-2*ni-2……所有的ai、ai-1、ai-2……不就都是0或1吗?(因为是二进制),注意审题,题中满足条件的数像这样表示,那么ai就只能是0或1,否则就不满足条件。回归正题:比方说593,变成8进制就是(1121)8,那么2就是第一个非1、0数位,那么就会把它变成(1111)8,这是为什么呢?我们会轻易的发现(1111)8到(1121)8区间内是绝对没有满足条件的数的,所以无需再找。其实说白了这个操作并不是转进制,而是1是能选,0是不选罢了,这个操作只是找到最大的区间内符合条件的数,刚好能当二进制罢了。

      哎,我也是厉害了,竟然能对着这行代码耗了一中午。不说了,直接上代码,详见注释:

     1 #include<iostream>
     2 using namespace std;
     3 int x,y,k,b,s[1000],f[1000][1000];
     4 int turn(int a,int b)
     5 {
     6     int cnt=0,ans=0;
     7     while(a)//转成b进制 
     8     {
     9         s[++cnt]=a%b;
    10         a/=b;
    11     }
    12     for(int i=cnt;i>=1;i--)
    13     {
    14         if(s[i]>1)//找到第一个不是0、1的数位 
    15         {
    16             for(int j=i;j>=1;j--)
    17             s[j]=1;//全部改成1,这是最大的比a小的符合要求的数 
    18             break;
    19         }
    20     }
    21     for(int i=1;i<=cnt;i++) if(s[i]) ans=ans|(1<<(i-1));//这是位运算,在二进制中相当于十进制的加法 
    22     return ans;
    23 }
    24 void init()//预处理 
    25 {
    26     f[0][0]=1;//这个千万别忘了 
    27     for(int i=1;i<=31;i++)
    28     {
    29         f[i][0]=1;//因为选0个“1”所以只有0,0,0,0,0,0……这条路径可以 
    30         for(int j=1;j<=i;j++) f[i][j]=f[i-1][j]+f[i-1][j-1];//详见上文 
    31     }
    32 }
    33 int calc(int num,int j)
    34 {
    35     int cnt=0,ans=0;
    36     for(int i=31;i>0;i--)
    37     {
    38         if((1<<i)&num)//(位运算)如果当前这一位是1 
    39         {
    40             cnt++;
    41             if(cnt>j) break;//已经多了,就不必找了 
    42             num=num^(1<<i);//将这一位变成0,对后面有用 
    43         }
    44         if((1<<(i-1))<=num)//如果下一位是1 
    45         ans+=f[i-1][j-cnt];//加上相应的左子树的值 
    46     }
    47     if(cnt+num==j) ans++;//如果刚好够,那么当前路径也要计入 
    48     return ans;
    49 }
    50 int main()
    51 {
    52     cin>>x>>y>>k>>b;
    53     x=turn(x,b);y=turn(y,b);
    54     init();
    55     cout<<calc(y,k)-calc(x-1,k);//前缀和 
    56     return 0;
    57 }
  • 相关阅读:
    git 基础操作 && 在"develop"分支修改动但不想提交切换到"master"分支(最后)
    html、css面试题 持续更新中......
    JS面试题 持续更新中......
    盒子模型
    Tomcat环境设置及第一个JSP程序实现
    嵌入式V3s交叉编译 tslib和QT4.8.7,并使用Qt Creator编译项目
    嵌入式全志V3s荔枝派板卡移植 aircrack-ng
    为嵌入式全志V3s荔枝派板卡添加USB MT7601U(小米随身WIFI)驱动
    为嵌入式mplayer移植添加ALSA音频驱动(全志V3s荔枝派zero)
    Makefile知识点总结
  • 原文地址:https://www.cnblogs.com/TFLS-gzr/p/11190702.html
Copyright © 2020-2023  润新知