题解 P1633 【二进制】
dalao 都用 DP,我发一发贪心的题解。
DP 题硬生生被做成了思维题
首先,不考虑长度限制,只考虑两个数字的 1 的个数。设 A 中 1 的个数为
如果
在这种情况下,我们需要通过进位消掉
容易发现,可以简单地使用连续进位来消掉任意个数的 1:
11111
+ 1
------
100000
消掉的 1 的个数即为第一个加数的长度。
严格来说,消掉的 1 的个数的范围为
11111 1
+ 111111
--------------
100000000000
(空格为 0)
可行性可以保证。然后就是考虑最优性。
很显然,对于这样一组连续进位,两个加数在连续进位之前的位必须都是 0。也就是说,算式里面最多只能有一组连续进位。如果有两组连续进位,可以将这两组合并,然后偷掉一个位(全 0 位显然可以直接抠掉),这样两个加数都会变小,和也会变小。
唯一的进位处就在那个连续进位的地方。
同理,这个连续进位必须在最高位。如果不是的话,可以交换顺序使它在最高位,然后偷掉一个位。
基本框架就确定了,由于消掉的 0 的个数确定,连续进位的长度也确定了,然后只需要在这个基础上贪心地将 1 用完就好了。
在这个连续进位式骨架上,我们可以做的事情包括将一个连续进位中的改成 1,以及在后面跟单独一个 1。容易证明,最优的解就是尽量用 1 填那个进位中的空,如果还有多余的 1,就在后面跟。
举个例子,例如
1111
+ 1
----
10001
然后,还有
1111
+11111
------
111101
程序指示 30+31=61,符合条件,并且正确。
代码如下:
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
const int l = max({
32-__builtin_clz(a),
32-__builtin_clz(b),
32-__builtin_clz(c)
});
const int an = __builtin_popcount(a);
const int bn = __builtin_popcount(b);
const int cn = __builtin_popcount(c);
if(an+bn<cn || !cn)
{
printf("-1\n");
continue;
}
if(an+bn-cn == 0)
{
printf("%d\n",allone(cn)); // cn个1
continue;
}
if(an+bn-cn+1+max(2*cn-an-bn,0) > l) // 计算最终长度的式子
{
printf("-1\n");
continue;
}
const int diff = an+bn-cn;
int ans = 1<<diff;
for(int i=diff+1; i<an+bn; ++i)
{
if(i-diff < diff) // 往空里填数
{
ans |= 1<<(i-diff);
}
else // 往末尾加数
{
ans = (ans<<1)|1;
}
}
printf("%d\n",ans);
然后交上去,就会发现这份代码只能得 20 分。
如果
111
+111
----
1110
实际上,加式是这个:
1111
+ 11
-----
10101
这个只需要注意一下就好了。稍微修改一下代码,加一个特判,就可以 A 了。时间复杂度 2ms-3ms,就是程序运行的基础时间。
附 AC 代码:
#include <cstdio>
#include <algorithm>
using namespace std;
inline int slen(int an,int bn,int cn) // 计算最终长度
{
return max({an+bn-cn+1+max(2*cn-an-bn,0),an+1,bn+1});
}
inline int allone(int dig) // dig 个 1 组成的数字
{
return (1<<dig)-1;
}
int main()
{
int t;
scanf("%d",&t);
for(int asdf=1; asdf<=t; ++asdf)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
const int l = max({
32-__builtin_clz(a),
32-__builtin_clz(b),
32-__builtin_clz(c)
});
const int an = __builtin_popcount(a);
const int bn = __builtin_popcount(b);
const int cn = __builtin_popcount(c);
if(an+bn<cn || !cn)
{
printf("-1\n");
continue;
}
if(an+bn-cn == 0)
{
printf("%d\n",allone(cn));
continue;
}
if(slen(an,bn,cn) > l)
{
printf("-1\n");
continue;
}
const int diff = an+bn-cn; // 连续等式的长度
const int flen = slen(an,bn,cn); // 最终结果的长度
int ans = (1<<(flen-1))+allone(flen-diff-1); // 最终结果的骨架(连续进位式再加上末尾的1)
for(int i=flen; i<an+bn; ++i)
{
ans |= 1<<(i-diff); // 因为末尾的 1 已经被加上,可以直接往空格里填 1
}
printf("%d\n",ans);
}
}