CodeTON Round 2 (Div. 1 + Div. 2, Rated, Prizes!) A-E
阅读原文时间:2023年07月09日阅读:3

比赛链接

A

知识点:思维,模拟。

发现 \(b\) 串第一个字符是 \(1\) 则只能使用 max , \(0\) 则只能使用 min ,随后只需要模拟到 \(a\) 串剩余 \(m\) 个字符时停止即可,然后比对两串。

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

空间复杂度 \(O(n+m)\)

#include <bits/stdc++.h>
#define ll long long

using namespace std;

bool solve() {
    int n, m;
    cin >> n >> m;
    string a, b;
    cin >> a >> b;
    if (b[0] == '0') for (int i = 1;i <= n - m;i++) a[i] = min(a[i], a[i - 1]);
    else if (b[0] == '1') for (int i = 1;i <= n - m;i++) a[i] = max(a[i], a[i - 1]);
    if (a.substr(n - m, m) == b) return true;
    else return false;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << "NO" << '\n';
        else cout << "YES" << '\n';
    }
    return 0;
}

B

知识点:贪心。

一个一个走过去,记录走过的最大值和最小值,一旦大于两倍 \(x\) 则说明要换中心点 \(v\) ,记录换的次数即可。

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

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

#include <bits/stdc++.h>
#define ll long long

using namespace std;

bool solve() {
    int n, x;
    cin >> n >> x;
    int mi = 2e9, mx = 0, cnt = 0;
    for (int i = 1, tmp;i <= n;i++) {
        cin >> tmp;
        mi = min(mi, tmp);
        mx = max(mx, tmp);
        if (mx - mi > 2 * x) cnt++, mx = mi = tmp;
    }
    cout << cnt << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

知识点:贪心。

注意到所有非感染者区间每秒都会被感染两个(从左右端点扩展,少于两个直接消失),因此考虑从大区间端点开始保护,每次更新 \(delta\) 代表每个区间减少了多少,则真实区间长度是原长减去 \(delta\)。

因此若真实区间大于等于2,则在两个端点各保护一个,答案加区间长度减一(一个端点保护完,另一个端点会感染一个),\(delta\) 加 \(4\);若区间长度等于1,则只能保护一个,直接退出;若区间已经小于等于0,没有能保护的,直接退出。

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int a[100007], l[100007];

bool solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= m;i++) cin >> a[i];
    sort(a + 1, a + 1 + m);
    l[1] = n - (a[m] - a[1] + 1);
    for (int i = 2;i <= m;i++) l[i] = a[i] - a[i - 1] - 1;
    sort(l + 1, l + m + 1, [&](int a, int b) {return a > b;});
    int delta = 0, ans = 0;
    for (int i = 1;i <= m;i++) {
        int rl = max(l[i] - delta, 0);
        if (rl == 0) break;
        if (rl == 1) {
            ans++;
            break;
        }
        ans += rl - 1;
        delta += 4;
    }
    cout << n - ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

知识点:枚举,前缀和,思维。

通常这种题是找不变量。

发现经过操作1以后,前缀和的总和不变;而经过一次操作2后,前缀和的总和会减少1。

因此统计每个数组的前缀和总和,找到前缀和总和最小的就是特殊数组,少了多少就是操作了多少次。

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

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

#include <bits/stdc++.h>
#define ll long long

using namespace std;

ll ssum[100007];

bool solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n;i++) {
        ll sum = 0;
        ssum[i] = 0;
        for (int j = 1;j <= m;j++) {
            ll x;
            cin >> x;
            sum += x;
            ssum[i] += sum;
        }
    }
    cout << min_element(ssum + 1, ssum + n + 1) - ssum << ' ';
    cout << *max_element(ssum + 1, ssum + n + 1) - *min_element(ssum + 1, ssum + n + 1) << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

E

知识点:图论,dp。

题目不难理解为,每秒所有点都会流出一个量到所有自己的下一个点,什么时候流完。比如,若 \(u\) 有三个下一个节点 \(v_1,v_2,v_3\) ,则一秒后 \(u\) 的值减一,而 \(v_1,v_2,v_3\) 都加一。

因为是DAG,所以图中每个点的量总是向出度为 \(0\) 的节点方向流动,最终出度为 \(0\) 的点的流完时间就是答案。

而对于一个节点的流完时间,取决于他所有上一个节点流完时间的总和加上自己节点的量,因为一个节点每秒只能流出一个量,只要上一个节点没流完,那这个节点就会一直存在量。因此,设 \(dp[i]\) 为编号 \(i\) 的节点的流出时间,则节点 \(u\) 与其上一个节点 \(v_i\) 有转移方程:

\[dp[u] = \sum dp[v_i] + a[u]
\]

但是有一种情况,如 10-0-1-0-0-null ,显然流完时间是 \(14\) ,但通过上面计算得到的是 \(11\) 。因为上述方程直接考虑从第一秒到最后一秒,整个图的量的转移是连续的,即量是持续减少,而没有考虑中间可能存在断点,但显然这是不确定的,这个例子就是。

因此需要使整个图的量堆积在出口前,使得量的流出是连续的。由于整个图有 \(n\) 个节点,至多 \(n-1\) 秒可以让量流经图中所有点(这里模拟 \(\geq n-1\) 秒都没问题),因此先模拟 \(n\) 秒,等到量都连起来了,再dp计算总时间即可。

模拟时候用正图,dfs时候反图,因此存两个图。

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

空间复杂度 \(O(n+m)\)

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int mod = 998244353;
vector<int> a, dp;
vector<int> g1[1007], g2[1007];    ///g1正向模拟用,g2反向dfs用

int dfs(int u) {
    if (dp[u]) return dp[u];
    dp[u] = a[u] % mod;
    for (auto v : g2[u])
        dp[u] = (0LL + dp[u] + dfs(v)) % mod;
    return dp[u];
}

bool solve() {
    int n, m;
    cin >> n >> m;
    a = dp = vector<int>(n + 1, 0);
    for (int i = 1;i <= n;i++) cin >> a[i], g1[i].clear(), g2[i].clear();
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g1[u].push_back(v);
        g2[v].push_back(u);
    }
    if (count(a.begin() + 1, a.end(), 0) == n) {
        cout << 0 << '\n';
        return true;
    }
    for (int i = 1;i <= n;i++) {///保证所有量都在出口前靠拢,防止空节点挡在其他节点之前产生计算错误
        vector<int> t = a;///临时,防止某个量持续传递
        for (int u = 1;u <= n;u++) {
            if (a[u]) {
                t[u]--;
                for (auto v : g1[u]) t[v]++;
            }
        }
        a = t;
        if (count(a.begin() + 1, a.end(), 0) == n) {
            cout << i << '\n';
            return true;
        }
    }
    int root = 0;
    for (int i = 1;i <= n;i++) if (!g1[i].size()) root = i;
    cout << (dfs(root) + n) % mod << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}