CF Round 767 Div2 题解

A题 Download More RAM

总计 T 组数据(1T100)。

现在我们有大小为 k 的内存,外加 n 个扩容器:第 i 个扩容器在载入的时候需要消耗 ai 的内存(载入完毕之后就不消耗了),然后永久给内存容量增加 bi

问我们至多可以将内存扩大到多少?

1n100,1k,ai,bi103

显然,我们将扩容器按照 a 的大小排序,优先用小的即可。

#include<bits/stdc++.h> using namespace std; const int N = 110; int n, k; struct Node { int a, b; bool operator < (const Node &rhs) const { return a < rhs.a; } } t[N]; void solve() { //read cin >> n >> k; for (int i = 1; i <= n; ++i) cin >> t[i].a; for (int i = 1; i <= n; ++i) cin >> t[i].b; //solve sort(t + 1, t + n + 1); for (int i = 1; i <= n; ++i) { if (t[i].a <= k) k += t[i].b; else break; } cout << k << endl; } int main() { int T; cin >> T; while (T--) solve(); return 0; }

B题 GCD Arrays

T 组数据(1T105)。

给定 a,l,r,则 {an} 为一个 [l,r] 上面的连续数列(例如 l=3,r=7,则 {an}={3,4,5,6,7})。

我们可以进行一种操作:从数列中选择任意两个数,然后从数列中删除他俩,随后向数列中插入这两个数的乘积。问,我们能否在进行不超过 k 次操作的情况下,使得 gcd(a)>1

1lr109,0krl

序列长度为 1 的时候,看看这个唯一的数是不是 1 就行了。

序列长度大于 1 时,那就是看整个数列缩到最后之后,每个数都有一个大于 1 的质因数。因为初始数列连续,那么不难想到这个质因数为 2(让这个质因数为 3 或者更大的数,难度要比 2 大得多)。

如果序列长度为偶数,那么奇偶一一配对即可,反之则加一减一来调一调(对于长度为 2 或者 3 的数列似乎也适用)。

#include<bits/stdc++.h> using namespace std; bool solve() { int l, r, k; scanf("%d%d%d", &l, &r, &k); if (k == 0) return l == r && l != 1; int len = r - l + 1; if (len % 2 == 0) return len / 2 <= k; else { if (l % 2 == 0) return len / 2 <= k; else return len / 2 + 1 <= k; } } int main() { int T; cin >> T; while (T--) puts(solve() ? "YES" : "NO"); return 0; }

C题 Meximum Array

总计 T 组数据(1T100)。

对于非负整数序列 {an},记 mex(a) 为最小的且未在 a 中出现的非负整数。

现在给定一个数列 {an},问我们能否将其划为几段,使得各段的 mex 组成的新数列的字典序最大(并输出它)?(遵循字符串的字典序定义)

n2105,0ain

抛开算法不谈,我们先看看怎么构造这玩意。

我们假设我们已经处理好了前 L1 个数,现在准备处理 [L,n] 上面的数。

  1. 显然,我们要处理下,看看这部分的 mex 是多少。
  2. 随后,我们压缩右区间 R,即找出最小的 R,使得 [L,R] 上面的 mex[L,n] 相同。
  3. L=R+1,继续循环,直到 L=n+1 为止。

但是很离谱的是,每一轮的复杂度都能够达到最坏 O(n),而整个程序最坏能进行 n 轮,即总复杂度 O(n2),这显然无法接受,必须降到 O(nlogn) 或者 O(n)

对于处理 mex,我们有这样一种策略:我们首先预处理出 [1,n] 上面的 mex(开一个 vis 数组(计数用的,并非纯粹判断这个数是否填过),填完之后从 0 到 n 扫一遍)。每次移动左端点 L 时,我们依次清理在 vis 里面的记录,如果过程中发现 visx=0x<Mex,那么我们将 Mex 更新为 x

可惜的是,我们只完成了左端点的优化,对于右端点的移动没啥方法,最坏复杂度依然为 O(n2)

法一:树状数组维护区间和

我们尝试更改一下套路,让右端点的移动从左向右进行,这样就实现了双指针,框架复杂度可以达到 O(n),里面还可以掺一些 O(logn) 的操作。从正向角度,我们不难想到一种策略:我们再开一个 vis 数组(这里不计数,纯粹用 01 表示有无),随后使 RL 开始,逐渐推进,当 i=0x1visi=xvisx=0 时,则 [L,R] 上面的 mex 就是 x。又要修改又要查询,那真就只有树状数组和线段树了(注意,这两个数据结构正常都是维护 [1,n] 上面的,所以对于题目中那种需要维护 [0,n] 的,需要手动维护下标为 0 的部分或者整体平移区间,还有就是这个 vis 不是随便加的,如果本来为 1 就别继续加,本来为 0 就别继续减啥的,反正小细节亿堆)。

#include <bits/stdc++.h> using namespace std; const int N = 200010; int n, a[N]; int vis[N]; int Mex; // struct TreeArray { int arr[N]; inline int lowbit(int x) { return x & (-x); } int ask(int x) { x++; int res = 0; for (; x; x -= lowbit(x)) res += arr[x]; return res; } int query(int l, int r) { return ask(r) - ask(l - 1); } int getv(int x) { return query(x, x); } void add(int x, int val) { if ((getv(x) == 1 && val == 1) || (getv(x) == 0 && val == -1)) return; x++; for (; x <= n + 1; x += lowbit(x)) arr[x] += val; } } ta; // int getR(int L, int mex) { for (int i = L; i <= n; ++i) { ta.add(a[i], 1); if (ta.query(0, mex - 1) == mex && ta.getv(mex) == 0) return i; } return n; } void clear(int L, int R) { for (int i = L; i <= R; ++i) { vis[a[i]]--; ta.add(a[i], -1); if (vis[a[i]] == 0 && a[i] <= Mex) Mex = a[i]; } } int ans[N]; void solve() { //read scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); //init memset(vis, 0, sizeof(int) * (n + 1)); memset(ta.arr, 0, sizeof(int) * (n + 2)); for (int i = 1; i <= n; ++i) vis[a[i]]++; for (int i = 0; i <= n; ++i) if (vis[i] == 0) { Mex = i; break; } //two pointer int L = 1, cnt = 0; while (L <= n) { int R = getR(L, Mex); ans[++cnt] = Mex; clear(L, R); L = R + 1; } //output printf("%d\n", cnt); for (int i = 1; i <= cnt; ++i) printf("%d ", ans[i]); puts(""); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

法二:set 维护

为了一个简单的需求来写一个树状数组太逆天了(昨天还给我调了将近半小时,最离谱的是还没调出来),所以我们尝试一下别的更简单的数据结构。我们综合一下,分析一下需求,发现我们需要:

  1. 对于一个 bool 数列求和(值仅有 01,且查询的区间固定)
  2. 能够快速的进行 bool 加
  3. 用完之后可以光速清零

我们不妨在开桶记录的时候,只有小于 Mex 的才统计,然后单独开一个 cnt 变量来判断是否 i=0x1visi=x(考虑到题目保证有解,那么一些极端情况就不考虑了)。不过普通的基于数组的桶,清零是基于值域的,比较麻烦,所以我们用 set 或者 map 进行代替。

#include <bits/stdc++.h> using namespace std; const int N = 200010; int n, a[N], vis[N], Mex; // int getR(int L, int mex) { set<int> s; int cnt = 0; for (int i = L; i <= n; ++i) { if (a[i] < Mex) s.insert(a[i]); if (s.size() == Mex) return i; } } void clear(int L, int R) { for (int i = L; i <= R; ++i) { vis[a[i]]--; if (vis[a[i]] == 0 && a[i] <= Mex) Mex = a[i]; } } int ans[N]; void solve() { //read scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); //init memset(vis, 0, sizeof(int) * (n + 1)); for (int i = 1; i <= n; ++i) vis[a[i]]++; for (int i = 0; i <= n; ++i) if (vis[i] == 0) { Mex = i; break; } //two pointer int L = 1, cnt = 0; while (L <= n) { int R = getR(L, Mex); ans[++cnt] = Mex; clear(L, R); L = R + 1; } //output printf("%d\n", cnt); for (int i = 1; i <= cnt; ++i) printf("%d ", ans[i]); puts(""); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

D题 Peculiar Movie Preferences

T 组数据(1T100)。

给定 n 个长度至多为 3 的字符串,问能否从中选几个重新排列,以组成一个新的回文串?

n105

本题有一个不是很显然,不好证明,但是多玩几组样例之后会隐约发现的结论:如果能的话,只需要至多两个字符串即可凑成一个新的回文串。

证明的话,详情可见官方题解,我这里简单说两个感性想到的点:

  1. 如果有长度为 1 的串,那么自己就是回文串,也就不需要拼,所以需要超过 2 个字符串凑成的回文串,子串必然都是长度为 2 或 3
  2. 对于这个长回文串,显然仅保留头尾两个字符串,同样是一个回文串(22,23,32,33四种情况均成立)

如果仅需要一个字符串就可以拼成,那么我们直接对 n 个字符串一一扫一遍即可。

如果需要两个,分类讨论:

  1. 22 或者 33 型,看看自己的反转串有没有出现过(开个 map 就行)
  2. 23 或者 32 型,把那个长度为 2 的反转过来,然后看看是不是谁的前缀或者后缀

注意,这玩意必须按照原顺序组成回文串,所以 23 和 32 在遍历时候必须按照一定顺序(顺序或者倒序),详情看代码。

#include<bits/stdc++.h> using namespace std; const int N = 100010; int n; string str[N]; string Rev(string s) { reverse(s.begin(), s.end()); return s; } bool solve() { //read cin >> n; for (int i = 1; i <= n; ++i) cin >> str[i]; //solve //type1 for (int i = 1; i <= n; ++i) if (str[i] == Rev(str[i])) return true; //type2 map<string, int> vis1; for (int i = 1; i <= n; ++i) vis1[str[i]] = 1; for (int i = 1; i <= n; ++i) if (vis1.find(Rev(str[i])) != vis1.end()) return true; //type3 map<string, int> vis2; for (int i = n; i >= 1; --i) { if (str[i].length() == 3) vis2[str[i].substr(1, 2)] = 1; else if (vis2.find(Rev(str[i])) != vis2.end()) return true; } //type4 map<string, int> vis3; for (int i = 1; i <= n; ++i) if (str[i].length() == 3) vis3[str[i].substr(0, 2)] = 1; else if (vis3.find(Rev(str[i])) != vis3.end()) return true; return false; } int main() { ios::sync_with_stdio(false); int T; cin >> T; while (T--) cout << (solve() ? "YES" : "NO") << endl; return 0; }

__EOF__

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