P9575 「TAOI-2」喵了个喵 Ⅳ

发布时间 2023-08-22 19:38:57作者: One_JuRuo

思路

考试的时候打死没想出来,一直在想暴力和质因数分解,我实在是太弱了,比赛后看了官方题解才恍然大悟,于是来写篇题解。

首先是一些特殊点:

  1. \(n\) 是偶数时,显然 \(x\) 可以取 \(1\),这样 \(\gcd\) 就都是 \(1\),然后随便平分就好了。恭喜你,你获得了 \(2\) 分。

  2. \(n\) 不是偶数,且 \(a_i\) 均为奇数,那么无论怎么 \(\gcd\) 都是奇数,所以 \(\gcd\) 之和也一定是奇数,那必然无法划分,那么就是无解。恭喜你,你又获得了 \(8\) 分(蒟蒻的我比赛的时候连这都没想到,痛失 \(8\) 分)。

  3. \(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]);
}