CodeTON Round 2 题解

写在前面

时隔近两年,这个 Codeforces赛后题解 系列续更了(
但是估计后面很长一段时间又要咕咕咕了

终于没掉分了,还上了一波小分,感动。可惜 E 题没能完成绝杀。还是太菜了 /kk

CF1704A Two 0-1 Sequences

首先不要看错题,只有 \(a\) 序列是可以操作的,\(b\) 序列是不会变的。而 \(a\) 的操作又要删除元素,所以 \(m\le n\) 才能使 \(a\) 有可能 变成 \(b\)。(赛时看题目浏览得比较匆忙,没注意到题目保证 \(m \le n\),所以代码中有一处特判,删去不影响)

由于最后 \(a\) 的长度从 \(n\) 变为 \(m\),而每次操作只能改变第一个元素,所以原序列 \(a\)\(b\) 的后 \(m-1\) 位必须相同。

\(a\) 的两个操作可以达到的效果是,使删去前面 \(x\) 位后得到的第一位可以取前 \(x+1\) 位的任意值。因此只需要再 check 一下 \(b\) 的第 \(1\) 位在 \(a\) 的前 \(n-m+1\) 位中有没有出现就好了。

时间复杂度 \(O(n)\),代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 55;
int n, m;
char a[maxn], b[maxn];

void work()
{
    scanf("%d%d", &n, &m);
    scanf("%s%s", a + 1, b + 1);
    if(n < m)  //前面提到的特判,删去不影响。
    {
        puts("No");
        return;
    }
    for(int i = n; i >= n - m + 2; --i)
        if(a[i] != b[m + i - n])
        {
            puts("No");
            return;
        }
    for(int i = n - m + 1; i >= 1; --i)
        if(a[i] == b[1])
        {
            puts("Yes");
            return;
        }
    puts("No");
    return;
}

int main()
{
    int T;
    cin >> T;
    while(T--) work();
    return 0;
}

CF1704B Luke is a Foodie

本题和 CF1304C Air Conditioner 的思想比较相似,不妨在切完本题后去切一下这题。

把题意抽象并且形象化一下,就是说,有一个区间 \([v-x, v+x]\) 可以通过改变 \(v\) 的值移来移去,我们要尽可能地把 \(a_i\) 框进去。

为了把 \(a_1\) 框进去,\(v\) 的范围为 \([a_1-x, a_1+x]\)
在不考虑 \(a_1\) 的情况下,为了把 \(a_2\) 框进去,\(v\) 的范围为 \([a_2-x,a_2+x]\)
那么如果这两个限制区间有交集,我们就可以通过调整 \(v\) 的初始值把 \(a_1, a_2\) 一次性框进去,\(v\) 的范围就是这两个区间的交集;
如果这两个限制区间没有交集,那么就必须改变 \(v\) 的值,\(ans\) 增加 \(1\),总的限制区间设置为 \([a_2-x,a_2+x]\)
此后类似。

总结一下思路:计算把 \(a_1,a_2,\cdots,a_k\) 都框进去的 \(v\) 的限制区间 \([L,R]\)。对其后的 \(a_{k+1}\),若 \([a_{k+1}-x,a_{k+1}+x]\)\([L,R]\) 有交集,那么就调整 \(L\)\(R\);若没有交集,那么 ans++\([L,R]\) 设为 \([a_{k+1}-x,a_{k+1}+x]\)

代码如下,时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int n;
ll a[maxn], x;
inline int read();

void work()
{
    n = read();
    x = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    ll L, R; int ans = 0;
    L = a[1] - x; R = a[1] + x;
    for(int i = 2; i <= n; ++i)
    {
        ll l = a[i] - x, r = a[i] + x;
        if(r < L || l > R)
        {
            ans++;
            L = l; R = r;
            continue;
        }
        L = max(L, l);
        R = min(R, r);
    }
    printf("%d\n", ans);
    return;
}

int main()
{
    int T;
    cin >> T;
    while(T--) work();
    return 0;
}

inline int read()
{
    int x = 0; bool f = true; char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') f = false; ch = getchar(); }
    while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return f ? x : -x;
}

CF1704C Virus

思考一下传染的过程:\(m\) 个初始感染点会把 \(n\) 个点分隔成 \(m\) 个区间(当然有的区间长度为 \(0\)),每个区间在不加任何干预的情况下会每次感染最左端和最右端的点,不断向中间推进直至全部感染。

要让感染的点最少,也就是让健康的点最多。

怎么样才能让健康的点最多呢?

古人云:“求木之长者,必固其根本;欲流之远者,必浚其泉源;思国之安者,必积其德义。”要从源头抓起防患于未然。

只要在每一段先堵上最左边的传染源再堵上最右边的传染源,就能让中间的点全部不被感染。

那么不难想到一个贪心的思想:先保最长的区间。

时间复杂度 \(O(m\log m)\),代码见下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm = 1e5 + 10;
int n, m, a[maxm], b[maxm];
inline int read();

void work()
{
    n = read();
    m = read();
    for(int i = 1; i <= m; ++i) a[i] = read();
    sort(a + 1, a + m + 1);
    b[1] = n - a[m] + a[1] - 1;
    for(int i = 2; i <= m; ++i)
        b[i] = a[i] - a[i - 1] - 1;
    sort(b + 1, b + m + 1);
    reverse(b + 1, b + m + 1);  //懒人式排序,从大到小
    //其实是赛时智障了一不小心搞成从小到大排序了
    //于是直接加了个reverse补救一下
    int ans = 0, t = 0;
    for(int i = 1; i <= m; ++i)
    {
        ll inf = 2LL * t;  //infected
        if(b[i] - inf <= 0) continue;
        if(b[i] - inf <= 2)  //这种情况只能保一个点
        {
            ans++;
            t++;
            continue;
        }
        ans += b[i] - inf - 1;
        //先堵上左边,然后损失一个点,再堵右边
        t += 2;
    }
    cout << n - ans << endl;
    return;
}

int main()
{
    int T;
    cin >> T;
    while(T--) work();
    return 0;
}

inline int read()
{
    int x = 0; bool f = true; char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') f = false; ch = getchar(); }
    while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return f ? x : -x;
}

CF1704D Magical Array

一道 Never Gonna Give You Up 式的 Rick Astley 诈骗题

一道思维好题。

乍一看很难找到突破口——复原数组 \(b\) 显然是不现实、不可行的。

那么思考的重点便是:

  • 所有由操作 \(1\) 得到的数组,有什么共同的特征?
  • 由操作 \(2\) 得到的数组,如何与操作 \(1\) 得到的数组区别开来?
  • 既然要计数,那么能否把数组映射为一个数,从而既达到判定又达到计数的目的?

目标:将数组处理成一个值,使得进行一次操作 \(1\) 不会改变这个值,而进行一次操作 \(2\) 会将这个值增加 \(1\)

这种想法属实是妙手偶得的神来之笔。

这其实是一个 hash 的思想,也就是,构建映射。

那么思路呼之欲出了:计算 \(\sum\limits_{j=1}^n j \times c_j\)。这个值满足我们的目标。

时间复杂度 \(O(nm)\),代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, m;
vector<ll> c[maxn];
ll v[maxn];

void work()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
    {
        c[i].clear();
        c[i].resize(m + 1);
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            cin >> c[i][j];
    fill(v + 1, v + n + 1, 0);
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            v[i] += j * c[i][j];
    ll mx = 0;
    int idx;
    for(int i = 1; i <= n; ++i)
    {
        if(v[i] > mx)
        {
            idx = i;
            mx = v[i];
        }
    }
    cout << idx << ' ';
    for(int i = 1; i <= n; ++i)
        if(v[i] != mx)
        {
            ll ans = mx - v[i];
            cout << ans << endl;
            break;
        }
    return;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while(T--) work();
    return 0;
}

CF1704E Count Seconds

赛时把题意读错了,凉了。

整个流程就好比是一个漫水的过程。

众所周知,在没有什么思路的时候,比较好的办法是手玩模拟一下样例。

不妨先思考没有点权为 \(0\) 的情况:入度为 \(0\) 的点只减不增,把自己的点权贡献给出边连向的点,点权减到 \(0\) 之后删去,从而又有新的一批点只减不增……

可不就是拓扑排序吗! 只要把拓扑序在前面的点的点权一直传递下去到汇点,就结束了,这部分很好算。

但问题出现了:我们发现汇点有可能点权为0,这个时候不会把点权排出去,不能用上面的方法直接算。

然而经过 \(n\) 轮后,所有的点的点权一定可以传递到汇点,假如在这前 \(n\) 轮中不是所有点的点权都变成了 \(0\),那么一定会使得汇点点权 \(>0\)

上代码,复杂度 \(O(nm+n^2)\)。一些细节见代码。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int mod = 998244353;
int n, m;
vector<int> g[maxn];
int in[maxn], a[maxn], add[maxn];
int ans;
bool del[maxn];
queue<int> q;
int tpo[maxn], tim;
inline int read();

void bruteforce()
/*
这里a和add的值都不要取模
因为可能涉及a是998244353的倍数的情况,取模后会出现错误
而且此处不取模是不会溢出的
*/
{
    ans++;
    fill(add + 1, add + n + 1, false);
    for(int i = 1; i <= n; ++i)
        if(a[i] > 0)
            for(int v : g[i])
                add[v]++;
    for(int i = 1; i <= n; ++i)
        if(a[i] > 0)
            a[i]--;
    for(int i = 1; i <= n; ++i) a[i] += add[i];
    return;
}

void toposolve()
{
    tim = 0;
    for(int i = 1; i <= n; ++i)
        if(in[i] == 0)
            q.push(i);
    while(!q.empty())
    {
        int p = q.front(); q.pop();
        tpo[++tim] = p;
        for(int v : g[p])
            if((--in[v]) == 0)
                q.push(v);
    }
    for(int i = 1; i <= n; ++i)
        a[i] %= mod;
    for(int i = 1; i <= n; ++i)
    {
        for(int j : g[tpo[i]])
            a[j] = (a[j] + a[tpo[i]]) % mod;
    }
    ans = (ans + a[tpo[n]]) % mod;
    return;
}

void work()
{
    ans = 0;
    n = read(); m = read();
    fill(in + 1, in + n + 1, 0);
    fill(del + 1, del + n + 1, false);
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i) g[i].clear();
    for(int i = 1; i <= m; ++i)
    {
        int x, y;
        x = read(); y = read();
        g[x].push_back(y);
        in[y]++;
    }
    for(int i = 1; i <= n; ++i)
    {
        bool flag = true;
        for(int j = 1; j <= n; ++j)
            if(a[j] != 0)
            {
                flag = false;
                break;
            }  //记得check,可能没跑满n轮就结束了
        if(flag)
        {
            cout << ans << endl;
            return;
        }
        bruteforce();
    }
    toposolve();
    cout << ans << endl;
    return;
}

int main()
{
    int T;
    T = read();
    while(T--) work();
    return 0;
}

inline int read()
{
    int x = 0; bool f = true; char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') f = false; ch = getchar(); }
    while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return f ? x : -x;
}

写在后面

太难了后面的题,
大钟楼下送快递,
一边开摆一边寄。
(押韵成功)

做不来,不写了。

posted @ 2022-08-01 22:10  Andrewzdm  阅读(122)  评论(1)    收藏  举报