CF Round 768 Div2 题解

A题 Min Max Swap

T 组数据。(1T100

给定两个长度为 n 的数列 {an},{bn},你现在可以进行不限量次操作,每次选择一个 p(1pn),并交换 ap,bp,问怎样可以使得两个数列的分别的最大值的乘积最大(不输出方案,只输出最大值)?

1n100,1ai,bi104

有一个显然的思路:找出最大值所在的数组,然后让另外一个数组里面的值尽可能小即可。

简化一下,就是:把每个位置上面小的数放在 a 里面,大的数放在 b 里面,遍历一次即可,复杂度 O(n)

#include<bits/stdc++.h> using namespace std; const int N = 110; int n, a[N], b[N]; int solve() { //read cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 1; i <= n; ++i) cin >> b[i]; //solve for (int i = 1; i <= n; ++i) if (a[i] > b[i]) swap(a[i], b[i]); int Max1 = 0, Max2 = 0; for (int i = 1; i <= n; ++i) Max1 = max(Max1, a[i]), Max2 = max(Max2, b[i]); return Max1 * Max2; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }

B题 Fun with Even Subarrays

T 组数据。(1T2104

给定一个长度为 n 的数组 {an},我们可以进行如下操作:

选择一个长度为 2k 的区间,然后将后 k 个数字平移到前 k 个位置上面(后 k 个位置上面值不改变),详情可以见原题。

问,至少需要多少次操作,可以使得整个数组的值变成一样?

n2105,1ain

显然,最后一个位置的值不可能被变成其他值,那么我们可以将数组转化一下,如果原位置的值等于 an 就记为 0,反之记为 1。那么问题就变成了:怎么在最快状态下使得整个数组变为 0?

我们直接贪心:记一个指针 p,从 n 开始,不断右移,当 [p,n] 内部不是全 0 时,给 p 加上 1,然后记 L=max(1,2p(n+1)),然后将区间 [L,p1] 上面全部置为 0,同时 res 加上 1,再让 p=L1,直到 p=0 为止,此时 res 的值就是最少操作次数。

关于怎么判断一个区间是不是全 0,以及怎么快速修改每个位置的值,我考场上套的树状数组,总复杂度 O(nlogn),其实直接遍历一遍就行了,复杂度 O(n)

//树状数组版本代码 #include<bits/stdc++.h> using namespace std; const int N = 200010; int n, a[N]; // int t[N]; inline int lowbit(int x) { return x & (-x); } int ask(int x) { int res = 0; for (; x; x -= lowbit(x)) res += t[x]; return res; } void add(int x, int val) { for (; x <= n; x += lowbit(x)) t[x] += val; } int query(int l, int r) { return ask(r) - ask(l - 1); } // void change(int l, int r) { for (int i = l; i <= r; ++i) if (query(i, i) == 1) add(i, -1); } int solve() { //read scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); //solve memset(t, 0, sizeof(int) * (n + 1)); for (int i = 1; i <= n; ++i) if (a[i] != a[n]) add(i, 1); int res = 0, p = n - 1; while (p >= 1) { if (query(p, n) > 0) { p++; int L = max(1, 2 * p - n - 1); change(L, p - 1); p = L - 1; res++; } else p--; } return res; } int main() { int T; scanf("%d", &T); while (T--) printf("%d\n", solve()); return 0; }
#include<bits/stdc++.h> using namespace std; const int N = 200010; int n, a[N]; void change(int l, int r) { for (int i = l; i <= r; ++i) a[i] = 0; } int solve() { //read scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); //solve for (int i = 1; i <= n; ++i) a[i] = a[i] != a[n]; int res = 0, p = n - 1; while (p >= 1) { if (a[p] > 0) { p++; int L = max(1, 2 * p - n - 1); change(L, p - 1); p = L - 1; res++; } else p--; } return res; } int main() { int T; scanf("%d", &T); while (T--) printf("%d\n", solve()); return 0; }

C题 And Matching

T 组数据。(1T400

给定 n 个数 [0,n),保证 n 是 2 的幂,问能否给这 n 个数找到一种配对方式,使得 i=1n/2ai&bi=k,并输出该方案?

4n216,0k<n

经过长时间观察,我发现了一种 k=0 时的构造方案:(0,n1),(1,n2),,(n21,n2) (这种方案是纯观察到的,但是存在性不容置疑:对每个数,直接寻找按位反差的那个值来配对即可)

如果 0<k<n1,我们发现找出下面两组: (0,n1),(k,nk1),然后交换一下,变为 (0,nk1),(k,n1)即可,别的值不变,这两组的值之和增加了 k ,刚好满足要求。

k=n1 的时候,我一开始以为无解,然后 WA 了几发之后老老实实去找方案:我们选择 (0,n1),(1,n2) 两组变换为 (0,1),(n2,n1),这样可以增加 n2;随后,我们选择 (2,n3),(3,n4) 两组,变化为 (2,n4),(3,n3)。从二进制上面不难发现,仅有最后一位出现了变化,其他位置保持不变,所以答案增加 1,刚好凑成 n1,成功。(n=4 的时候无解,因为此时仅有两组,不够分)

#include<bits/stdc++.h> using namespace std; const int N = 100010; int vis[N]; void solve(int n, int k) { if (k == n - 1) { if (n == 4) puts("-1"); else { printf("%d %d\n%d %d\n%d %d\n%d %d\n", 0, 1, n - 2, n - 1, 2, n - 4, 3, n - 3); for (int i = 4; i < n / 2; ++i) printf("%d %d\n", i, n - 1 - i); } return; } memset(vis, 0, sizeof(int) * (n + 1)); if (k) { vis[0] = vis[n - 1] = vis[k] = vis[n - k - 1] = 1; printf("%d %d\n%d %d\n", 0, n - k - 1, k, n - 1); } for (int i = 0; i < n / 2; ++i) if (!vis[i]) printf("%d %d\n", i, n - i - 1); return; } int main() { int T; cin >> T; while (T--) { int n, k; cin >> n >> k; solve(n, k); } return 0; }

D题 Range and Partition

T 组数据。(1T3104

给定一个长度为 n 的数列 {an} 和正整数 k,问能否找到一个区间 [x,y],使得我们能将数列分成恰好 k 段,每段里面,在该区间内部的数要比不在该区间的数要多?若存在,请输出 yx 最小值。

1kn2105,1ain

存在性毋庸置疑:让这个区间为 [1,n] 即可,然后想怎么分。

我们先来看看,在确定了区间之后,如何判断该区间是否合法:我们构建一个新数组 {bn},原数组中在该区间内部的数标记为 1,反之标记为 0,那么问题就变成了:给定一个 01 数组,问能否恰好分成 k 组,每组内部 1 的数量都大于 0?

今年牛客冬令营的第一场 F 题有一个类似的题目,问能否分成若干段,使得每段中位数都大于某个值。那场的题解和标准证明都在这:题解,我们直接搬运一下结论:当区间内 1 的个数减去 0 的个数大于等于 k 时存在分组方案,直接扫一遍,复杂度 O(n)。(本题还需要输出方案,道理也差不多

如果我们暴力枚举 x,y,那么总复杂度就是 (n3),如果枚举 x 并二分 y,那么复杂度就是 O(n2logn)

上面因为搬运结论,刚给忘了一个东西:我们并不需要扫一遍整个数组,只需要先给排个序,然后每次统计数量的时候直接两次二分即可,这样复杂度就降到了 O(nlog2n),应该可以通过了。

#include<bits/stdc++.h> using namespace std; const int N = 200010; int n, k, a[N], b[N]; bool check(int x, int y) { int l = lower_bound(a + 1, a + n + 1, x) - a; int r = upper_bound(a + 1, a + n + 1, y) - a - 1; return (r - l + 1) - (n - (r - l + 1)) >= k; } void output(int x, int y) { printf("%d %d\n", x, y); int L = 1; for (int i = 1; i < k; ++i) { int cnt = 0; for (int R = L; R <= n; ++R) { x <= b[R] && b[R] <= y ? cnt++ : cnt--; if (cnt > 0) { printf("%d %d\n", L, R); L = R + 1; break; } } } printf("%d %d\n", L, n); } void solve() { //read & init scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), b[i] = a[i]; sort(a + 1, a + n + 1); //solve int res = 1e9 + 10, ansx = 0, ansy = 0; for (int x = 1; x <= n; ++x) { int l = x, r = n + 1; while (l < r) { int mid = (l + r) >> 1; if (check(x, mid)) r = mid; else l = mid + 1; } if (r != n + 1 && res > r - x) { res = r - x, ansx = x, ansy = r; } } //output output(ansx, ansy); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

不过我们还发现:x,y 均符合单调性质,所以我们还可以双指针,将枚举 x,y 的复杂度降到 O(n),这样就可以在 O(nlogn) 的复杂度内解决该问题了。

其实我们甚至不用直接枚举 x,y,而是直接枚举对应的下标:在排序好的数组上枚举 i,j,保证 i,j 之间的数要多于不在该范围的数,然后不断更新 x=ai,y=aj 即可。这个 i,j 甚至不需要双指针,直接预先算好那个合适的距离 len=n+k2 即可。

#include<bits/stdc++.h> using namespace std; const int N = 200010; int n, k, a[N], b[N]; void output(int x, int y) { printf("%d %d\n", x, y); int L = 1; for (int i = 1; i < k; ++i) { int cnt = 0; for (int R = L; R <= n; ++R) { x <= b[R] && b[R] <= y ? cnt++ : cnt--; if (cnt > 0) { printf("%d %d\n", L, R); L = R + 1; break; } } } printf("%d %d\n", L, n); } void solve() { //read & init scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), b[i] = a[i]; //solve sort(a + 1, a + n + 1); int res = 1e9 + 10, x = 0, y = 0; int len = (n + k + 1) / 2; for (int L = 1; L + len - 1 <= n; ++L) { int R = L + len - 1; if (res > a[R] - a[L]) res = a[R] - a[L], x = a[L], y = a[R]; } //output output(x, y); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

__EOF__

本文作者cyhforlight
本文链接https://www.cnblogs.com/cyhforlight/p/15867491.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cyhforlight  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
历史上的今天:
2021-02-07 2021牛客寒假算法基础集训营3 解题补题报告
点击右上角即可分享
微信分享提示