CF Round 788 Div2 题解

比赛链接

A题 Prof. Slim(签到)

给定一个长度为 n 的数列 {an}(保证 ai0),我们可以对其进行若干次操作,每次操作都可以任意选择不同两项并交换他们的符号。

问,能否通过若干次操作,使得整个数列变为单调不降数列?

n105,|ai|109

我们观察发现两个性质:

  1. 不管怎么操作,每个位置的数的绝对值都不会改变
  2. 使得单调不降,至少得把所有负号全部移到最前面

那么思路就显然了:统计数列中负号的数量,记为 k,当数列前 k 位的绝对值单调不增,后 k 位单调不降时,则能够通过操作使得整个数列单调不降。整个数列扫一遍,复杂度 O(n)

#include <bits/stdc++.h> using namespace std; const int N = 200010; int n, a[N]; bool solve() { cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; int k = 0; for (int i = 1; i <= n; ++i) { if (a[i] < 0) ++k; a[i] = abs(a[i]); } for (int i = 2; i <= k; ++i) if (a[i] > a[i - 1]) return false; for (int i = k + 2; i <= n; ++i) if (a[i] < a[i - 1]) return false; return true; } int main() { int T; cin >> T; while (T--) puts(solve() ? "YES" : "NO"); return 0; }

B题 Dorms War(思维)

给定一个长度为 n 的字符串 s 和一个字符集合 v

接下来,对字符串不断进行如下操作,操作流程如下:

  1. 遍历所有字符,若该字符处于集合 v 中,则将其前一个字符打上标记
  2. 删除所有打上标记的字符

问,需要多少次操作之后,字符串不会再改变?(若操作次数为 k,则说明前 k 次操作后字符串长度都发生了改变,而 k+1 次及以后的操作则不会)

n2105

我们直接将其转换成 01 串(在集合内的字符为 1,反之为 0),将问题简化一下。

当串中仅包含一个 1 时,操作次数显然就是这个 1 前面 0 的个数。

当存在多个 1 时,我们发现,后面的 1 需要额外多操作一次,以和前面的 1 进行合并(合并本身不会改变 0 的数量)。

那么,整体操作流程如下:

  1. 将 01 串分割成若干仅包含一个 1,且 1 在最后的串(如 [0001001101] 就拆成 [0001],[001],[1],[01]
  2. 统计每个串中 0 的数量
  3. 对于非第一个串的 0 的数量要加上 1,作为操作次数
  4. 求出最大值,即为整个串的总操作次数

(这题卡 STL 就 nm 离谱,第一次在 CF 上面被常数制裁

#include<bits/stdc++.h> using namespace std; const int N = 200010; int n, k; char s[N]; int vec[N]; int solve() { //read & init scanf("%d%s", &n, s + 1); scanf("%d", &k); int vis[26]; memset(vis, 0, sizeof(vis)); for (int i = 1; i <= k; ++i) { char stp[3]; scanf("%s", stp); vis[stp[0] - 'a'] = 1; } //build int tot = 0, cnt = 0; for (int i = 1; i <= n; ++i) if (vis[s[i] - 'a']) vec[++tot] = cnt, cnt = 0; else ++cnt; //solve if (tot == 0) return 0; int res = -1; for (int i = 1; i <= tot; ++i) res = max(res, vec[i] + (i > 1)); return res; } int main() { int T; cin >> T; while (T--) printf("%d\n", solve()); return 0; }

C题 Where is the Pizza?(数学,并查集)

给定两个不同的长度为 n 的排列 {an},{bn}

接下来,我们要从这两个排列来构造新排列 {cn}

  1. 对于一些指定位置,例如 ck,我们规定其值必然为 ak 或者 bk 中的一个(题目数据给定)
  2. 其他位置(例如 ci),我们可以选择这个位置是 ai 亦或是 bi

问,我们一共有多少种不同的选择方式?

n5105,答案对 109+7 取模,保证至少有一种方式

我们考虑位置没有指定的情况:

假定 a=[1,2,3,4],b=[3,1,2,4],我们令 c1=a1=1,那么 c2 必然为 2(因为 b2=1,已经选了,没法再选),随后 c3 必然是 3(因为 b2=2 也被选了)。

那么,我们注意到位置 1,2,3 组成了一个闭环:一旦某个位置上面的值被确定,那么剩下来的位置就跟着确定。那么,我们称这个闭环为一个联通块(模拟一下就可以发现,这个联通块不会随你的选择而变化,它是固定的)。我们记联通块数量为 t,那么答案就是 2t

考虑到有些位置被确定了,那么我们直接看这几个位置在哪个联通块上,直接统计答案的时候跳过这几个对应的连通块即可。

#include<bits/stdc++.h> using namespace std; #define LL long long const int N = 500010; int n, a[N], b[N], c[N], d[N]; vector<int> e[N]; // int fa[N]; int find(int x) { if (x == fa[x]) return x; return fa[x] = find(fa[x]); } bool merge(int x, int y) { x = find(x), y = find(y); if (x != y) fa[x] = y; return x != y; } // LL solve() { //read cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 1; i <= n; ++i) cin >> b[i]; for (int i = 1; i <= n; ++i) cin >> d[i]; //init for (int i = 1; i <= n; ++i) fa[i] = i, e[i].clear(); for (int i = 1; i <= n; ++i) e[a[i]].push_back(i), e[b[i]].push_back(i); //solve int tot = n; for (int i = 1; i <= n; ++i) if (merge(e[i][0], e[i][1])) tot--; set<int> s; for (int i = 1; i <= n; ++i) s.insert(find(i)); for (int i = 1; i <= n; ++i) if (d[i] || (a[i] == b[i])) s.erase(find(i)); //calc LL res = 1, mod = 1e9 + 7; for (int i = 0; i < (int)s.size(); ++i) res = res * 2 % mod; return res; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }

D题 Very Suspicious(数学,二分)

这题建议直接看原题题面

这题二分没得跑,问题是,我们怎么求出:n 条线最多可以搞出多少个三角形?

我们将画的线按照三类来分,随后发现:每次加上某一条种类的线,就能多上另外两种线的数量乘上 2 的三角形。

按照数学规律,最合适的方式就是三种线轮流加,类似如下形式:

0 : 0 0 0 1 : 1 0 0 2 : 1 1 0 3 : 1 1 1 4 : 2 1 1 5 : 2 2 1 ...

那么,我们可以直接尝试公式递推得到 f(3k) 的值,然后模拟出 f(3k+1),f(3k+2) 即可,规律如下:

f(3k)=6k2f(3k+1)=f(3k)+4kf(3k+2)=f(3k+1)+4k+2=f(3k)+8k+2

#include<bits/stdc++.h> using namespace std; #define LL long long LL f(int n) { LL k = n / 3, res = 6 * k * k; switch (n % 3) { case 0: return res; case 1: return res + 4 * k; case 2: return res + 8 * k + 2; default: return -1; } } int solve() { int n; cin >> n; int l = 1, r = 100000; while (l < r) { int mid = (l + r) >> 1; if (f(mid) >= n) r = mid; else l = mid + 1; } return l; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }

__EOF__

本文作者cyhforlight
本文链接https://www.cnblogs.com/cyhforlight/p/16245934.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cyhforlight  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示