CF教育场131 题解

官方题解

A题 Grass Field(签到)

给定一个两行两列的方格,每个格子里面会有草(或者没东西,就是空地)。每次操作,我们可以选定一行和一列,随后将这行和这列上的草全部清空。

给定一种情况,问至少需要多少次操作才能完全清空草。

四个格子都没草的话就是 0 次,四个格子都有的话就 2 次。

其他任何情况,都仅仅需要一次操作即可(1个草和3个草显然,2个草可以视为3个草的弱化版)。

#include<bits/stdc++.h> using namespace std; int solve() { int tot = 0, x; for (int i = 0; i < 4; ++i) cin >> x, tot += (x == 1); if (tot == 0) return 0; if (tot == 4) return 2; return 1; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }

B题 Permutation(思维)

给定一个排列 bn 和常数 d,倘若有 xi 满足 dbi=bi+1(1i<n),那么这个排列的满意度就是 x

给定 n,要求求出满意度最高的排列(d 可以自己选),多解的情况下输出任意一种即可。

2n2105

严格证明不太会,但是从感性角度,显然应该取 d=2,然后尽可能的造等比数列,就像下面的代码一样(有点 O(nlogn) 那种素数筛的感觉了)。

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

C题 Schedule Management(贪心,二分)

给定 n 个工人和 m 个任务,现在要把任务分配给工人们去做,每个工人独立的同时开始工作,最后的总花费时间为所有工人所花时间的最大值(取 max)。不过,现在存在着任务熟练度的情况,即如果第 ai 个工人去做第 i 个任务,那么只需要一个时间单位,否则则需要两个时间单位。

给定 n,m,{an},试求出所花时间的最小值。

1n,m2105

显然,时间可以二分,那么我们直接看咋判断给定时间能不能完成就行了。

根据贪心原则,每个任务都应该优先分给熟练这个任务的人(除非他确实负载满了),随后我们记一下剩下来没被分配的任务数量为 tot,接着扫一遍所有工人,有剩的时间就塞点任务,最后看看有没有剩下来的任务就行。

二分范围:至少需要时间 1,至多需要时间 2m

综上,时间复杂度为 O((n+m)logm)

#include<bits/stdc++.h> using namespace std; const int N = 200010; int n, m, a[N], h[N]; bool check(int x) { memset(h, 0, sizeof(int) * (n + 1)); int tot = 0; for (int i = 1; i <= m; ++i) if (h[a[i]] == x) ++tot; else h[a[i]]++; //这里有个小坑 //如果你只管一直减,最后才判断tot是不是小于等于0 //极限数据的情况下会直接爆int //所以我们采取了边减边判断的方式,这样就不会爆int了 for (int i = 1; i <= n; ++i) { tot -= (x - h[i]) / 2; if (tot <= 0) return true; } return false; } int solve() { cin >> n >> m; for (int i = 1; i <= m; ++i) cin >> a[i]; int l = 0, r = 2 * m + 1; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }

D题 Permutation Restoration(数论分块,贪心)

给定正整数 n 和排列 {an},我们就可以构造出新数列 bi=iai

现在给定了 n{bn},要求反向构造出合法的排列 {an}

1n5105,0bi5105,保证一定有合法解

显然,对于每个位置,我们需要知道它对应可以填哪些数,而这些数是连续的,那只要求出对应区间即可。

这是数论分块的内容(虽然我tm看了半小时 OI Wiki都没推出来公式,麻了),这里给出推导:

bi=iaibiiai<bi+1ibi+1<aiibi

得到了每个位置对应的区间,我们看咋进行匹配(要是能缩小数据范围到 500 就好了,直接二分图匹配啥的莽上去)。

考试时候有这么一个思路:按照左端点第一关键字,右端点第二关键字的方式来排序,随后贪心选择即可,但是显然,这有一个 bug:[2,3],[2,4],[2,5],[3,3](纯展示用,应该不会真的出现这种数据)。

考虑优化一下:按照左端点来排序,但是同时需要动态为左端点进行修改(例如上面,把 2 赋给第一个区间后,后面所有区间的左端点全部改成 3 或者更大的值)。

注意到,只有当选择的数达到 x 时,才会把左端点为 x 的数放进备选空间,而一旦放进去后,则只和右端点有关,那么我们就按照这个规律,创建一个新集合 S,每当枚举到一个数 x 时,把所有新的符合要求的点放进去,然后从中选取右端点最小的即可。(因为保证有解,所以实际上还可以省略很多花里胡哨操作)

总时间复杂度:O(nlogn)

#include <bits/stdc++.h> using namespace std; const int N = 500010; int n, a[N], b[N]; struct Node { int l, r, id; } nd[N]; set<pair<int, int> > s; void solve() { scanf("%d", &n); for (int i = 1; i <= n; ++i) cin >> b[i]; for (int i = 1; i <= n; ++i) { nd[i].r = b[i] > 0 ? i / b[i] : n; nd[i].l = i / (b[i] + 1) + 1; nd[i].id = i; } sort(nd + 1, nd + n + 1, [](const Node &a, const Node &b) { return a.l < b.l; }); for (int i = 1, j = 1; i <= n; ++i) { for (; nd[j].l == i && j <= n; ++j) { int id = nd[j].id; s.insert({nd[j].r, nd[j].id}); } a[s.begin()->second] = i; s.erase(s.begin()); } for (int i = 1; i <= n; ++i) printf("%d ", a[i]); puts(""); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

__EOF__

本文作者cyhforlight
本文链接https://www.cnblogs.com/cyhforlight/p/16463760.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cyhforlight  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示