【UER #11】科考工作
- 给定 个 中的整数,选择恰好 个使得和为 的倍数。保证 为质数。
- 。
本质思想就是若在 意义下,对于任意 集合 满足 (即对于任意集合中的数 ,),那么 必定 。
这个结论还是比较显然的,考虑 在 意义下构成了长度为 的环,因为一定有 ,故如果有数不在 中,那么一定能找到相邻的两个数 ,使得 ,得到矛盾。
回到原问题,先任意选出一个数扔一边,再给剩下的 个数两两配对,使得配对的两数不同。如果无法达到说明有一种数字出现次数 ,直接输出 个这种数即可。
之后先选择每个配对中的第一个数,结合一开始扔出去的数得到和为 的 个数。对于每个配对 处理出反悔的贡献 ,现在变成要求选出集合 的一个子集使得和为 。
要做的就是循环 01 背包,根据一开始的结论每次至少在集合中新增一个数,所以一定有解。
至少可以用 bitset 优化到 ,利用 二分 + Hash 每次在循环背包上精准定位 的位置可以做到 (动态 Hash 需要树状数组),同时还有两倍常数(会有同样多个 被定位到)。
更进一步的,这里每次让集合新增恰好一个数就足够了,并且已知 ,只需要找到任意一个不在集合中的数 ,解出 ,那么序列在 这个首为 尾为 的部分中一定有相邻的 ,这是经典的可二分的结构。
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;
}
实际上,这题是可以扩展到 为非质数的!主要问题是此时 的环大小可能 ,所以上述做法不成立,但是既然有了质数的解法,不妨尝试分解质因数。
假设 ,其中 为质数。现在序列中有 个数。考虑先任意扔出 个数,再将剩下的每 个分为一组,构成 组,且每一组的和均为 的倍数。这个是可以做到的,因为每次可以直接选出 个数,和扔出的 个数组成 个数,从中选 个构成 的倍数就是之前的问题,处理后将不用的 个数继续扔出来即可。
之后就会有 组数,以及 个数。直接不要那 个数,而是在组中选,因为每组的和都是 的倍数,所以可以直接将 每组的和除以 作为一个新的序列,递归到在 个数中选择 的倍数的子问题。
因为不断递归必然回到质数问题,而质数一定有解,所以这样一定有解!
于是我们就成功构造性的证明了:给定任意 个 中的整数,都能够选择恰好 个使得和为 的倍数。这个事实。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现