【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\)的区间就是:

\[[1,ta+tb-2\times t],[ta+ta-2\times t+2,tc+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\)的区间就是:

\[[1,tb-t],[tb-t+2,tb-t+ta],[tb+1,tb+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\)的区间就是:

\[[2,tc],[t+1,t+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;//最后输出答案前记得判断
}
posted @ 2020-02-02 11:56  TheLostWeak  阅读(...)  评论(...编辑  收藏