CF1945 H GCD is Greater

CF1945 H GCD is Greater

纯属码力题。
首先发现 \(\gcd\) 肯定数字越多越小。
然后与值数字越多越小。
所以结论就是选择两个数字取 \(\gcd\),剩下的数字分到另一个集合。
那么问题就变成了找出一对数字,它们的 \(\gcd\) 和剩下的数字的与做差值最大化。

怎么,是不是感觉这个问题还是非常抽象。
于是又开始人类智慧了。

这里这里,如果某一位所有数字这一位都为 1,我们以下证明不考虑这些位,我们把这些位单独提出来。
发现如果数字非常多,与值很有可能等于 \(0\)
于是我们在二进制位上面考虑问题。现在考虑让与值为 \(0\)
我们把某一位为 \(0\) 的数字个数记下来。

  • 那么如果只有一个这一位为 \(0\) 的数字,那么这个数字肯定不能取。(取了那么另一个集合中所有数字这一位就是 \(1\) 了)。

  • 如果只有两个这一位为 \(0\) 的数字。那么这两个数字不能同时取(同理)。

也就是说最多只会有 \(\log(4\times 10^5)=19\) 条限制。
那如果不同数字的个数超过了 \(20\) 就一定可以使其与值为 \(0\)

我们预处理 \(al_i\) 表示所有数字中\(i\) 位为 0 的数字个数。
于是我们枚举最大公约数 \(d\),考虑将它的倍数全部提出来,集合称为 \(bs\),以外的数字集合称为 \(out\)
我们处理 \(in_i\) 表示集合 \(bs\)\(i\) 位为 0 的数字个数。
我们通过每一位的 \(al-in\) 可以快速求出集合 \(out\) 的与值。
然后考虑集合 \(in\) 中选择两个数字,分类讨论:

  • \(|bs|\) 小于等于 \(20\)
    我们暴力枚举两个数字。然后用前缀后缀与快速求出其他数字的与值(记得和 \(out\) 的与在做一遍 \(\And\) 运算),然后判断是否有解,时间复杂度 \(O(20\times 20)\)
  • \(|bs|\) 大于 \(20\)
    那么与值一定为 0,还是一样暴力扫两个值,最劣情况就是都是一个数字的限制,然后我们最后枚举到。(因为限制只有 20 个,所以复杂度是对的)。

那么这一题基本做完了。写起来当然爽
这一题的细节和易错点:

  1. 有一些位,所有数字这一位都是 \(1\),那么将这些位加起来为 \(s\),那么最大公约数就从 \(s+x+1\) 开始枚举。
  2. 本题有重复数字。可以先特判一下选两个重复数字的情况。后面的枚举也要记得考虑这种情况!!!!
  3. 对于某一位 \(0\) 的个数有为 \(1\)\(2\) 的限制,可以用变量 \(one,two\) 状压,位运算快速 check。
#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for (int i = (l); i <= (r); i++)
#define per(i,r,l) for (int i = (r); i >= (l); i--)
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define maxn(a, b) a = max(a, b)
#define minn(a, b) a = min(a, b)
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
const ll mod = 998244353;
mt19937 gen(114514);
ll rp(ll a,ll b) {ll res=1%mod;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}

const int V = (1<<20)-1;

void solve() {
	int n, c, m = 0;
	scanf("%d%d", &n, &c);
	VI a(n);
	for (auto &x : a) {
		scanf("%d", &x);
		maxn(m, x);
	}
	VI cnt(m + 1);
	for (auto x : a) cnt[x]++;
	VI suf(m + 2);
	suf[m + 1] = V;
	rep(i,1,m) suf[i] = cnt[i] ? i : V;
	per(i,m,1) suf[i] &= suf[i + 1];

	auto print = [&](const int &x, const int &y) {
		puts("YES");
		printf("2 %d %d\n", x, y);
		cnt[x]--; cnt[y]--;
		printf("%d", n - 2);
		rep(i,1,m) while (cnt[i]--) printf(" %d", i);
		puts("");
	};

	int pre = V;
	rep(i,1,m) {
		if (cnt[i] > 2) pre &= i;
		if (cnt[i] >= 2 && i > (pre & suf[i + 1]) + c) {
			print(i, i);
			return ;
		}
		if (cnt[i]) pre &= i;
	}

	VI al(21);
	int one = 0, two = 0;
	rep(i,1,m) if (cnt[i]) {
		rep(j,0,20) if (~i>>j&1) al[j] += cnt[i];
	}
	int s = 0;
	rep(j,0,20) {
		if (!al[j]) s |= 1 << j;
		else if (al[j] == 1) {
			one |= 1<<j;
		} else if (al[j] == 2) {
			two |= 1<<j;
		}
	}

	rep(d,s + c + 1,m) {
		VI bs, in(21);
		for (int b = d; b <= m; b += d) {
			if (cnt[b]) {
				bs.pb(b);
				rep(i,0,20) if (~b>>i&1) {
					in[i] += cnt[b];
				} 
			}
		}
		int out = 0;
		rep(i,0,20) if (al[i] == in[i]) {
			out |= 1 << i;
		}
		if (SZ(bs) <= 20) {
			suf = bs; suf.pb(V);
			per(i,SZ(bs)-1,0) suf[i] &= suf[i + 1];
			int l = V;
			rep(i,0,SZ(bs)-1) {
				int mid = V;
				if (cnt[bs[i]] > 1) mid &= bs[i];
				rep(j,i+1,SZ(bs)-1) {
					if (cnt[bs[j]] > 1) mid &= bs[j];
					if ((l & mid & suf[j + 1] & out) + c < d) {
						print(bs[i], bs[j]);
						return ;
					}
					mid &= bs[j];
				}
				l &= bs[i];
			}
		} else {
			rep(i,0,SZ(bs)-1) {
				int vi = V^bs[i];
				if (one & vi) break;
				rep(j,i+1,SZ(bs)-1) {
					int vj = V^bs[j];
					if (one & vj) break;
					if (vi & vj & two) break;
					print(bs[i], bs[j]);
					return ;
				}
			}
		}
	}
	puts("NO");
}

int main() {
	int T;
	scanf("%d", &T);
	while (T--)
		solve();
	return 0;
}
posted @ 2024-03-20 09:57  weirdoX  阅读(61)  评论(0)    收藏  举报