CF Round 788 Div2 题解

比赛链接

A题 Prof. Slim(签到)

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

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

\(n\leq 10^5,|a_i|\leq 10^9\)

我们观察发现两个性质:

  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\) 次及以后的操作则不会)

\(n\leq 2*10^5\)

我们直接将其转换成 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\) 的排列 \(\{a_n\},\{b_n\}\)

接下来,我们要从这两个排列来构造新排列 \(\{c_n\}\)

  1. 对于一些指定位置,例如 \(c_k\),我们规定其值必然为 \(a_k\) 或者 \(b_k\) 中的一个(题目数据给定)
  2. 其他位置(例如 \(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;
}
posted @ 2022-05-08 16:21  cyhforlight  阅读(17)  评论(0编辑  收藏  举报