【BZOJ3107】[CQOI2013] 二进制a+b(构造)
大致题意: 给定三个数\(a,b,c\),把它们转化为无前导\(0\)的二进制数,然后以位数最高的数为基准,给其余数加前导\(0\)补齐。接下来让你将\(a,b,c\)二进制下各位重排得到\(a',b',c'\),使得\(a'+b'=c'\),求最小的\(c'\)。
构造
一道构造题,大量分类讨论.......
我们设\(ta,tb,tc\)分别表示\(a,b,c\)二进制下\(1\)的个数,\(l\)表示最高的位数。
方便起见,我们强制\(ta\le tb\)。
显然,当\(ta+tb=tc\)时,答案在二进制下就是连续\(tc\)个\(1\)。
否则,我们考虑做二进制下加法,每一次进位,\(1\)的总数便会减少\(1\)个。
所以,如果\(ta+tb<tc\),就肯定无解。
不然,\(ta+tb>tc\),则我们设\(t=ta+tb-tc\),即所需进位的次数。
然后就是分类讨论了。
分类讨论
当\(ta\ge t\)且\(tb\ge t\)时,我们可以从\(a,b\)中各拎出\(t\)个\(1\),把它们放在一起做加法,就实现了进位\(t\)次。这个运算的结果在二进制下就是\(t\)个连续的\(1\)和一个\(0\)。
然后,\(a,b\)中分别剩下了\(ta-t\)和\(tb-t\)个\(1\),由于我们不能让它再进位,所以就必须将它们错开做加法。这个运算的结果就是\(ta+tb-2\times t\)个\(1\)。
也就是说,最后的答案将由\(t\)个连续的\(1\)和一个\(0\)、\(ta+tb-2\times t\)个\(1\)两部分组成。
由于要让答案最小,所以我们尽量让\(0\)靠左。
而且,容易发现答案的总位数是\((t+1)+(ta+tb-2\times t)=ta+tb-t+1=tc+1\)。
假设从右边开始首位为第\(1\)位,则最后答案中为\(1\)的区间就是:
当\(ta<t\)且\(tb\ge t\)时,首先把\(a\)中的\(1\)全部拎出来,再从\(b\)从同样拎出\(ta\)个\(1\)和它配合在一起做加法进位\(ta\)次。
但由于\(ta<t\),所以此时进位次数还不够,因此我们需要再从\(b\)中拎出\(t-ta\)个\(1\)放在这\(ta\)对\(1\)左侧,这样一来,\(ta\)对\(1\)经过加法进位后得到的最高位的那个\(1\)就会连带地让这\(t-ta\)个\(1\)全部进位。这个运算的结果在二进制下就是一个\(1\)、\(t-ta\)个\(0\)、\(ta-1\)个\(1\)和一个\(0\)。
而\(tb\)中我们一共拎出了\(ta+(t-ta)=t\)个\(1\),也就是还剩下\(tb-t\)个\(1\)。
同样容易发现,答案的总位数是\(1+(t-ta)+(ta-1)+1+(tb-t)=tb+1\)。
像前一种情况一样,由于要尽量让\(0\)靠左,所以最后答案中为\(1\)的区间就是:
当\(ta<t\)且\(tb<t\)时,发现如果按之前的方法去做就无法凑成\(t\)次进位。
假设我们需要从\(a,b\)中各拎出\(x\)个\(1\)让它们成对进位,而将\(a,b\)中剩下的\(1\)按照前面第二种情况中的方法连带进位,则就有:\(x+(ta-x)+(tb-x)\ge ta+tb-tc\)。化简得到\(x\le tc\)。
而根据\(ta<t\),即\(ta<ta+tb-tc\),得到\(tb>tc\),同理也能得到\(ta>tc\)。
所以,\(x\)一定能够取到\(tc\),且显然\(x\)越大答案越优(因为凑成的对数越多,答案的位数就越少)。
我们从\(a,b\)中各拎出\(tc\)个\(1\),然后把剩下的\(ta+tb-2\times tc=t-tc\)个\(1\)一股脑地堆在这\(tc\)对\(1\)的左侧。
加法后,就得到一个\(1\)、\(t-tc\)个\(0\)、\(tc-1\)个\(1\)和一个\(0\)。
所以,最后答案中为\(1\)的区间就是:
顺便提一下,在之前的做法中有可能最后得到的\(c'\)二进制下位数超过\(l\),显然与题意不符。
因此我们最后要判断\(c'<2^l\),才能输出答案。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define swap(x,y) (x^=y^=x^=y)
#define Fill(x,y) for(i=x;i<=y;++i) ans|=1<<(i-1)
using namespace std;
int a,b,c;
int main()
{
RI i,ta=0,tb=0,tc=0,l;scanf("%d%d%d",&a,&b,&c);
for(i=a;i;i&=i-1) ++ta;for(i=b;i;i&=i-1) ++tb;for(i=c;i;i&=i-1) ++tc;//统计1的个数
for(ta>tb&&swap(ta,tb),l=1;(1LL<<l)<=max(a,max(b,c));++l);//注意一定要1LL,否则2^31会挂掉
RI ans=0,t=ta+tb-tc;if(ta+tb==tc) {Fill(1,tc);goto End;}//以下是各种分类讨论
if(t<0) return puts("-1"),0;
if(ta<t&&tb<t) {Fill(2,tc);Fill(t+1,t+1);goto End;}
if(ta<t) {Fill(1,tb-t);Fill(tb-t+2,tb-t+ta);Fill(tb+1,tb+1);goto End;}
Fill(1,ta+tb-2*t);Fill(ta+tb-2*t+2,tc+1);
End:return ans<(1LL<<l)?printf("%d\n",ans):puts("-1"),0;//最后输出答案前记得判断
}