CodeForces Round 959 (PARTIAL) Solution

0. 闲话

欢迎来到有史以来最简单的一场 Div. 1 + Div. 2.

题有 \(8\) 道,AC \(5\) 道。

然而因为手速不够快惜败于 dzj 大佬。

这件事情告诉我们,不要在打 CF 的时候做其他事情。

详情见下。

1. Diverse Game / 红

给一个大小为 \(nm\) 的排列,让我构造一个大小为 \(nm\) 且每个元素都与原排列不同的排列。

好啊你 CF,你终于给了一次真正的签到题,之前你给的签到题思维难度起码到黄。

直接让每个元素模 \(nm\) 再加 \(1\) 即可。正确性显然。时间复杂度 \(O(nm)\)

#include <iostream>
using namespace std;
const int N = 5e4 + 10;
int n, m;
void run()
{
    scanf("%d%d", &n, &m);
    if (n == 1 and m == 1)
    {
        scanf("%*d");
        puts("-1");
        return;
    }
    for (int i = 1, x; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            scanf("%d", &x);
            printf("%d%c", x % (n * m) + 1, " \n"[j == m]);
        }
    }
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

2. Fun Game / 橙

给两个 \(01\) 序列 \(a\)\(b\),可以进行无限次操作使 \(a_l\)\(a_r\) 同时异或 \(a_1\)\(a_{r-l+1}\)。问将 \(a\) 通过操作转换为 \(b\) 的可行性。

我看到题目后想,这啥?第二题就上强度啊?

然后我想起了赛前刷的绝区零视频。我突然意识到,可以将 \(a\)\(b\) 内的前缀零看成“空洞”,因为根据题目的要求,前缀零无论进行多少次操作,长度都不会减小。

而在初始空洞后的所有元素,可以依靠空洞后的第一个 \(1\) 修改成任意的样子。

于是问题解决。核心代码一行。时间复杂度 \(O(n)\)

#include <iostream>
#include <string>
using namespace std;
const int N = 1e5 + 10;
int n;
string s, t;
void run()
{
    cin >> n >> s >> t;
    puts(s.find('1') <= t.find('1') ? "YES" : "NO");
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

3. Hungry Games / 黄

有一排蘑菇。

每次可以选择一个区间依次吃蘑菇,初始受毒性为 \(0\),吃掉第 \(i\) 个蘑菇会使得受毒性增加 \(a_i\)。每次吃掉一个蘑菇后,若受毒性超过一个常数 \(x\),则将受毒性归零。问有多少个区间使得依次吃完蘑菇后的受毒性不为 \(0\)

正难则反。虽然正也不难,但是反更好想。

结果不为 \(0\)=总区间数-结果为 \(0\)

计算方式类似拓扑排序。

\(d_i\) 表示以 \(i\) 为区间右端点时有多少个左端点 \(j\) 满足从 \(j\) 吃到 \(i\) 后受毒性为 \(0\)

遍历到一个位置 \(i\) 时,找到第一个满足 \(\sum_{j=i}^t a_j>x\) 的端点 \(t\),将 \(d_t\) 增加 \(d_i+1\)。因为,\(d_i\) 所记录的方案满足最终受毒性为 \(0\),而从 \(i\)\(t\) 的最终受毒性也是 \(0\),所以可以看作将两个区间拼接起来。最终受毒性仍然为 \(0\)

时间复杂度 \(O(n)\)

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
using ll = long long;
int n, k, r;
ll a[N], dp[N], cur, res;
void run()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", a + i);
    }
    memset(dp + 2, 0, n << 3);
    cur = 0;
    r = 1;
    for (int i = 1; i <= n; i++)
    {
        cur -= a[i - 1];
        while (r <= n and cur <= k)
            cur += a[r], r++;
        if (cur <= k)
            continue;
        dp[r] += dp[i] + 1;
    }
    res = n;
    res *= n + 1;
    res /= 2;
    for (int i = 2; i <= n + 1; i++)
    {
        res -= dp[i];
    }
    printf("%lld\n", res);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

4. Funny Game / 绿

有一个拥有 \(n\) 个节点的完全图。每个点有一个点权 \(a_i\)。点 \(i\) 和点 \(j\) 之间的边的边权为 \(|a_i-a_j|\)。要求找出图中的一个生成树,使得这个生成树中的第 \(i\) 条边的边权整除 \(i\)\(i\)\(1\) 开始计数。

当时我先于两位大佬 A 掉了第三题,然后觉得,对于一场 Div. 2 来说,这已经是我的极限了,于是去刷绝区零的视频。正当我欣赏格莉丝代理人秘闻的时候,我惊恐地发现那两位大佬已经 A 掉了这一题。然后我的大脑开始飞速运转。

于是突然想到 \(i\) 越大期望被整除的边就越少,考虑贪心+并查集。具体来说从大到小枚举每个 \(i\),计算每个 \(a_j\bmod i\) 的结果,如果发现有两个 \(a_j\) 余数相同但不在同一连通块内,就将这两个连通块合并。

当我发现它 A 了的时候,我的大脑炸了。

时间复杂度 \(O(n^2)\)

#include <iostream>
#include <list>
#include <cstring>
using namespace std;
const int N = 2e3 + 10;
int n, a[N], f[N], v[N], p;
bool fl;
using pii = pair<int, int>;
list<pii> ret;
inline int find(int x)
{
    return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x, int y)
{
    f[find(y)] = find(x);
}
void run()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        f[i] = i;
        scanf("%d", a + i);
    }
    ret.clear();
    for (int i = n - 1; i; i--)
    {
        memset(v, 0, i << 2);
        fl = false;
        for (int j = 1; j <= n; j++)
        {
            p = a[j] % i;
            if (!v[p])
            {
                v[p] = j;
                continue;
            }
            if (find(j) == find(v[p]))
                continue;
            merge(j, v[p]);
            ret.push_front({j, v[p]});
            fl = true;
            break;
        }
        if (!fl)
            return puts("NO"), void();
    }
    puts("YES");
    for (auto &[tx, ty] : ret)
        printf("%d %d\n", tx, ty);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

5. Wooden Game / 绿

有个由 \(k\) 棵树组成的森林,每次可以移除任意一个节点为根的子树,求所有移除的子树大小的最大或值。

打完上一题,我夹在两个大佬中间。我想,应该不会再有 A \(5\) 题那么离谱的 Div. 1 + Div.2 了罢。然后去看莱卡恩的代理人秘闻。

万万没想到,还没看一会,那两位大佬就又把这题 A 掉了。

吓得我赶紧回来使大脑飞速运转。

我本来想着各种复杂的 DP,然后就突然想到可以移除叶子。也就是说,一个大小为 \(x\) 的子树对答案的贡献可以是 \([1,x]\) 中的任意一个整数。

当我想到这时,我的脑子再一次被炸掉了。它给的树结构压根就没有用。

时间复杂度 \(O(n)\)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int n, a[N], t, res;
void run()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", a + i);
        for (int j = 1; j < a[i]; j++)
            scanf("%*d");
    }
    sort(a + 1, a + n + 1);
    t = 1;
    for (; t < a[n]; t = t * 2 + 1)
        ;
    res = 0;
    for (int i = n; i; i--)
    {
        while ((t > a[i]) or (t & res))
            t--;
        res |= t;
    }
    printf("%d\n", res);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
        run();
}

6. 后记

然后两位大佬的其中一位把第 \(6\) 题过了。

比赛完后发现,出题人竟然在第 \(5\) 题的题解中定义一个 trash 变量然后输入 \(n-1\) 次……真够幽默的。

然后飞升了。加了 \(117\)

+117

我只能说:

74TrAkToR 你干得好啊!

posted @ 2024-07-19 10:20  丝羽绫华  阅读(257)  评论(0编辑  收藏  举报