【UER #11】科考工作

link

  • 给定 2n1[0,n1] 中的整数,选择恰好 n 个使得和为 n 的倍数。保证 n 为质数。
  • n3×105

本质思想就是若在 modn 意义下,对于任意 x[0,n1] 集合 s 满足 ss+x=s(即对于任意集合中的数 y(y+x)modns),那么 s 必定 ={0,1,,n1}

这个结论还是比较显然的,考虑 0,x,2x,3x,,(n1)xmodn 意义下构成了长度为 n 的环,因为一定有 0s,故如果有数不在 s 中,那么一定能找到相邻的两个数 kx,(k+1)x,使得 kxs,(k+1)xs,得到矛盾。

回到原问题,先任意选出一个数扔一边,再给剩下的 2n2 个数两两配对,使得配对的两数不同。如果无法达到说明有一种数字出现次数 >n1,直接输出 n 个这种数即可。

之后先选择每个配对中的第一个数,结合一开始扔出去的数得到和为 sn 个数。对于每个配对 (a,b) 处理出反悔的贡献 d=ba,现在变成要求选出集合 d 的一个子集使得和为 s

要做的就是循环 01 背包,根据一开始的结论每次至少在集合中新增一个数,所以一定有解。

至少可以用 bitset 优化到 O(n2ω),利用 二分 + Hash 每次在循环背包上精准定位 01 的位置可以做到 O(nlog2n)(动态 Hash 需要树状数组),同时还有两倍常数(会有同样多个 10 被定位到)。

更进一步的,这里每次让集合新增恰好一个数就足够了,并且已知 0s,只需要找到任意一个不在集合中的数 c,解出 c=kd,那么序列在 0,d,2d,,kd 这个首为 1 尾为 0 的部分中一定有相邻的 (1,0),这是经典的可二分的结构。

code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

#define eb emplace_back
typedef pair<int, int> pii;
#define mp make_pair
#define fi first
#define se second

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

const int N = 3e5 + 10;
int n, a[N << 1], d[N]; pii b[N];
int f[N]; bool r[N];

int p[N << 1];
void solve1() {
	rep(i, 1, (n << 1) - 2) p[i] = i;
	sort(p + 1, p + (n << 1) - 1, [](int x, int y) {return a[x] < a[y];});
	
	rep(i, 1, n - 1) {
		if(a[p[i]] == a[p[i + n - 1]]) {
			rep(j, 1, n) printf("%d%c", p[i + j - 1], j == n ? '\n' : ' ');
			exit(0);
		}
		b[i] = mp(p[i], p[i + n - 1]);
		d[i] = (a[p[i + n - 1]] - a[p[i]] + n) % n;
	}
}

void exgcd(int a, int b, int &x, int &y) {
	if(!b) return x = 1, y = 0, void();
	exgcd(b, a % b, x, y);
	int z = x; x = y, y = z - y * (a / b);
}

int calc(int b, int a) {
	int x, y; exgcd(a, n, x, y);
	x = (x % n + n) % n;
	return 1ll * x * b % n;
}

void solve2() {
	
	f[0] = -1; int c = 1;
	rep(i, 1, n - 1) {
		int l = 1;
		int r = calc(c, d[i]);
		
		while(l < r) {
			int mid = (l + r) >> 1;
			int cur = 1ll * mid * d[i] % n;
			if(!f[cur]) r = mid; else l = mid + 1;
		}
		
		f[1ll * l * d[i] % n] = i;
		while(c < n && f[c]) c ++;
		if(c == n) break;
	}
	
	int s = a[(n << 1) - 1];
	rep(i, 1, n - 1) s = (s + a[b[i].fi]) % n;
	s = (n - s) % n;
	
	while(s) {
		r[f[s]] = true;
		s = (s - d[f[s]] + n) % n;
	}
}

int main() {
	n = read();
	rep(i, 1, (n << 1) - 1) a[i] = read();
	
	solve1();
	solve2();
	
	rep(i, 1, n - 1) printf("%d ", r[i] ? b[i].se : b[i].fi);
	printf("%d\n", (n << 1) - 1);
	return 0;
}

实际上,这题是可以扩展到 n 为非质数的!主要问题是此时 0,x,2x, 的环大小可能 <n,所以上述做法不成立,但是既然有了质数的解法,不妨尝试分解质因数。

假设 n=mp,其中 p 为质数。现在序列中有 2mp1=(2m1)p+(p1) 个数。考虑先任意扔出 p1 个数,再将剩下的每 p 个分为一组,构成 2m1 组,且每一组的和均为 p 的倍数。这个是可以做到的,因为每次可以直接选出 p 个数,和扔出的 p1 个数组成 2p1 个数,从中选 p 个构成 p 的倍数就是之前的问题,处理后将不用的 p1 个数继续扔出来即可。

之后就会有 2m1 组数,以及 p1 个数。直接不要那 p1 个数,而是在组中选,因为每组的和都是 p 的倍数,所以可以直接将 每组的和除以 p 作为一个新的序列,递归到在 2m1 个数中选择 m 的倍数的子问题。

因为不断递归必然回到质数问题,而质数一定有解,所以这样一定有解!

于是我们就成功构造性的证明了:给定任意 2n1[0,n1] 中的整数,都能够选择恰好 n 个使得和为 n 的倍数。这个事实。

posted @   LPF'sBlog  阅读(185)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示