CF Round 788 Div2 题解
作者:互联网
A题 Prof. Slim(签到)
给定一个长度为 \(n\) 的数列 \(\{a_n\}\)(保证 \(a_i\not=0\)),我们可以对其进行若干次操作,每次操作都可以任意选择不同两项并交换他们的符号。
问,能否通过若干次操作,使得整个数列变为单调不降数列?
\(n\leq 10^5,|a_i|\leq 10^9\)
我们观察发现两个性质:
- 不管怎么操作,每个位置的数的绝对值都不会改变
- 使得单调不降,至少得把所有负号全部移到最前面
那么思路就显然了:统计数列中负号的数量,记为 \(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\)。
接下来,对字符串不断进行如下操作,操作流程如下:
- 遍历所有字符,若该字符处于集合 \(v\) 中,则将其前一个字符打上标记
- 删除所有打上标记的字符
问,需要多少次操作之后,字符串不会再改变?(若操作次数为 \(k\),则说明前 \(k\) 次操作后字符串长度都发生了改变,而 \(k+1\) 次及以后的操作则不会)
\(n\leq 2*10^5\)
我们直接将其转换成 01 串(在集合内的字符为 1,反之为 0),将问题简化一下。
当串中仅包含一个 1 时,操作次数显然就是这个 1 前面 0 的个数。
当存在多个 1 时,我们发现,后面的 1 需要额外多操作一次,以和前面的 1 进行合并(合并本身不会改变 0 的数量)。
那么,整体操作流程如下:
- 将 01 串分割成若干仅包含一个 1,且 1 在最后的串(如 \([0001001101]\) 就拆成 \([0001],[001],[1],[01]\))
- 统计每个串中 0 的数量
- 对于非第一个串的 0 的数量要加上 1,作为操作次数
- 求出最大值,即为整个串的总操作次数
(这题卡 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\) 的排列 \(\{a_n\},\{b_n\}\)。
接下来,我们要从这两个排列来构造新排列 \(\{c_n\}\):
- 对于一些指定位置,例如 \(c_k\),我们规定其值必然为 \(a_k\) 或者 \(b_k\) 中的一个(题目数据给定)
- 其他位置(例如 \(c_i\)),我们可以选择这个位置是 \(a_i\) 亦或是 \(b_i\)
问,我们一共有多少种不同的选择方式?
\(n\leq 5*10^5\),答案对 \(10^9+7\) 取模,保证至少有一种方式
我们考虑位置没有指定的情况:
假定 \(a=[1,2,3,4],b=[3,1,2,4]\),我们令 \(c_1=a_1=1\),那么 \(c_2\) 必然为 2(因为 \(b_2=1\),已经选了,没法再选),随后 \(c_3\) 必然是 3(因为 \(b_2=2\) 也被选了)。
那么,我们注意到位置 \(1,2,3\) 组成了一个闭环:一旦某个位置上面的值被确定,那么剩下来的位置就跟着确定。那么,我们称这个闭环为一个联通块(模拟一下就可以发现,这个联通块不会随你的选择而变化,它是固定的)。我们记联通块数量为 \(t\),那么答案就是 \(2^t\)。
考虑到有些位置被确定了,那么我们直接看这几个位置在哪个联通块上,直接统计答案的时候跳过这几个对应的连通块即可。
#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)=6k^2 \\ f(3k+1)=f(3k)+4k \\ f(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;
}
标签:return,数列,int,题解,788,CF,3k,solve,操作 来源: https://www.cnblogs.com/cyhforlight/p/16245934.html