P9575 「TAOI-2」喵了个喵 Ⅳ
思路
考试的时候打死没想出来,一直在想暴力和质因数分解,我实在是太弱了,比赛后看了官方题解才恍然大悟,于是来蹭写篇题解。
首先是一些特殊点:
-
当 \(n\) 是偶数时,显然 \(x\) 可以取 \(1\),这样 \(\gcd\) 就都是 \(1\),然后随便平分就好了。恭喜你,你获得了 \(2\) 分。
-
当 \(n\) 不是偶数,且 \(a_i\) 均为奇数,那么无论怎么 \(\gcd\) 都是奇数,所以 \(\gcd\) 之和也一定是奇数,那必然无法划分,那么就是无解。恭喜你,你又获得了 \(8\) 分(蒟蒻的我比赛的时候连这都没想到,痛失 \(8\) 分)。
-
当 \(n\) 不是偶数时,这种情况比较难办,下面专门详细讲解。
首先容易发现 \(x=1\) 时,\(\gcd\) 之和就是 \(n\),但是 \(n\) 为奇数,显然不满足要求。
这样,我们可以尝试增大 \(x\) 的倍数。
假设扩大了 \(x\) 共 \(k\) 倍,那么 \(\gcd(a_i,x)\) 的变化一定是扩大 \(l\) 倍,其中 \(l\) 是 \(k\) 的因子,那么对于和的贡献是 \((l-1)\times \gcd(a_i,x)\)。
这样的话,只有 \(l\) 为偶数,\(\gcd(a_i,x)\) 为奇数时,贡献才能增加奇数,才能使和变为偶数。
所以我们扩大倍数扩大奇数倍是毫无作用的,扩大倍数含奇数因子意义也不大,所以我们要扩大的倍数只有 \(2^s,s\in \mathbb{Z}\) 最有效。
那么 \(s\) 取几最合适呢?
有两种方法,第一种是挨个试,试到 \(2^s\) 比最大的 \(a_i\) 还要大为止,不过没有试过,不清楚会不会 TLE。
第二种,我们可以随意构造一个同时含奇数、偶数的数列,如果奇数的个数奇数个,那就意味着 \(x\) 不能取 \(1\),如果把 \(x\) 扩大 \(2\) 倍,奇数的个数不会变,其余的 \(\gcd\) 要么不变,要么也扩大 \(2\) 倍,和仍然是偶数,所以这种情况一定是无解。那么,我们可以尝试把问题归纳到这种情况解决。
那么,怎么找到那个数呢,这里推荐一种比较方便的方法,那就是 \(lowbit\)。
\(lowbit(x)\) 可以取出 \(x\) 在二进制形式下的最末位的 \(1\) 以及后面的 \(0\)。举个例子,\(12\) 的二进制形式是 \(1100\),那么 \(lowbit(12)=4\)。(\(4\) 的二进制形式是 \(100\))
那么,怎么计算 \(lowbit\) 呢?
其实很简单,只需要一行代码就能搞定:inline int lowbit(int x){return x&(-x);}
原理就要涉及负数的编码了,负数都是正数的反码再加 \(1\) 得到的。
例如 \(12\) 的反码就是 \(0011\)(省略了前面若干位 \(1\)),再加 \(1\) 就是 \(0100\),再与原数进行按位与计算,就得到了 \(lowbit\)。
总而言之就是,二进制末尾有连续多少个 \(0\),反码就有连续多少个 \(1\),再加上一个 \(1\) 使其进位,最后与原数一样的就只有最后一个 \(1\) 及其后面的 \(0\)。
所以我们只需要把每个数都除以最小的 \(lowbit\) 就可以得到上述的情况。
我们再统计奇数数量,如果数量为偶,则有解;否则,无解。
那么,我们再确定 \(x=2\)(除后的,输出记得乘回去),问题就被转化为了有偶数个 \(1\),和奇数个 \(2\) 如何分配了。
只需要把两个 \(1\) 放在第一组,其他 \(1\) 平分,\(2\) 平分,然后把多出的一个 \(2\) 给第二组就好了。
AC 代码
#include<bits/stdc++.h>
using namespace std;
inline int lowbit(int x){return x&(-x);}
int n,a[100005],low=1000005,sum,flag,cnt1,cnt2=1;
int main()
{
scanf("%d",&n);
if(n%2==0)//先把n为偶的判断了
{
printf("1\n");
for(int i=1;i<=n/2;++i) printf("01");
exit(0);
}
for(int i=1;i<=n;++i) scanf("%d",&a[i]),low=min(low,lowbit(a[i]));//记录最小lowbit
for(int i=1;i<=n;++i) a[i]/=low,sum+=a[i]%2;
if(sum%2) printf("-1"),exit(0);
printf("%d\n",2*low);//记得乘回去
for(int i=1;i<=n;++i)
{
if(a[i]%2)
{
if(flag<2) flag++,a[i]=0;//flag记录在第一组单独放了多少个了
else a[i]=cnt1,cnt1^=1;//然后就是平均分配了
}
else
{
a[i]=cnt2,cnt2^=1;//从第二组开始平均分配
}
}
for(int i=1;i<=n;++i) printf("%d",a[i]);
}