Contestant~. Rank 1733. Rating +45(+295 -250).

乱猜结论场,猜不出来坐牢场

A. Subtraction Game

题意

给定两个人之间的博弈:每个人可以选择拿走 \(a\)\(b\) 个石头。

输出后手有必胜策略的石头的总数。

思路

很经典的博弈。

如果先手拿走 \(x\) 个石头后,后手都能拿走 \(n - x\) 个石头,那么后手必胜。

那么,\(n = a + b\)

时间复杂度:\(O(1)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 3e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int a, b;
    cin >> a >> b;
    cout << a + b << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

怎么有人放典题啊

B. Permutations & Primes

题意

给定排列的长度 \(n\),构造一个排列,满足所有连续子序列中 \(MEX\) 为质数的子序列最多。

\(MEX\) 代表不出现在区间中的最小正数。

思路

首先,我们希望 \(MEX\) 尽量不等于 \(1\),那么我们就需要将 \(1\) 放在中间。

其次,比 \(1\) 大的两个质数刚好是 \(2, 3\),也就是说,如果某个区间内包含了 \(1\),但不包含 \(2\)\(3\),那么它的 \(MEX\) 就一定是质数。

换句话说,我们希望 \(2, 3\) 尽可能不出现在区间中,因此我们将其放在两个端点。

那么,剩下的位置,任意放置即可,因为只要不包含 \(1\),就一定不是质数。

基于上述操作的考虑,在赛时可能会出现下面的猜测:

  1. 中间放 \(1\),左边升序放奇数,右边降序放偶数;

  2. 中间放 \(1\),依次在两端放上质数,剩下的填合数;

  3. ...

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

对应AC代码(猜测1)

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    for(int i=3;i<=n;i+=2) cout << i << ' ';
    cout << 1 << ' ';
    for(int i=n-n%2;i>=2;i-=2) cout << i << ' ';
    cout << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

对应AC代码(正解)

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    vector<int> ans(n);
    ans[0] = 2, ans[n - 1] = 3, ans[n / 2] = 1;
    int val = 4;
    for(int i=0;i<n;i++){
        if(ans[i]) continue;
        ans[i] = val ++;
    }
    for(auto e : ans) cout << e << ' ';
    cout << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

如果再胆大一点就不会卡着半小时不交题了(悲

C. Particles

题意

给定一个序列,定义一次操作如下:

  1. 选定一个元素;

  2. 将其删除;

  3. 将其左边和右边的元素合并,值变为两者之和

输出任意操作后,最后剩余的一个数的最大值。

思路

首先,我们不难发现,奇偶性不同的两个元素是不可能加在一起的。

其次,如果奇数位上出现负数,我们将其删除后,左右两者合并之后变为一个元素,那么这个元素变成偶数位上的元素,无法参与奇数位的运算了。

偶数位也是一样。

也就是说,我们只需统计奇数位和偶数位的和,遇到负数的时候,直接忽略即可。

当然,如果全都是负数,我们取负数的最大值。

很巧的是,赛时写了个连续子序列的 \(dp\),刚好顺手用 \(0\) 和负数取了个最大值,刚好就是正解(小声

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

对应AC代码(dp)

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 2);
    for(int i=2;i<=n+1;i++) cin >> a[i];
    vector<vector<int>> dp(n + 2, vector<int>(2, -inf));
    for(int i=2;i<=n+1;i++){
        dp[i][0] = max(dp[i - 2][0], dp[i - 2][1]);
        dp[i][1] = max(0ll, dp[i][0]) + a[i];
    }
    cout << max(max(dp[n][0], dp[n][1]), max(dp[n + 1][0], dp[n + 1][1])) << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

对应AC代码(正解)

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    int ans0 = 0, ans1 = 0, mx = -inf;
    for(int i=1;i<=n;i++) {
        int cur;
        cin >> cur;
        mx = max(mx, cur);
        if(cur < 0) continue;
        if(i % 2 == 0) ans0 += cur;
        else ans1 += cur;
    }
    cout << (mx < 0 ? mx : max(ans0, ans1)) << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

真就乱猜

D. Row Major

题意

给定字符串的长度 \(n\),对于所有 \(a \times b = n\),将字符串横向依次放入 \(a \times b\) 的矩阵中,输出一个字符串,满足所有矩阵的所有相邻字符都不相同,且字符的种数最小。

如 "tomato":

\(\mathtt{t\ \ \ \ t\ o\ \ \ \ t\ o\ m\ \ \ \ t\ o\ m\ a\ t\ o}\)

\(\mathtt{o\ \ \ \ m\ a\ \ \ \ a\ t\ o}\)

\(\mathtt{m\ \ \ \ t\ o}\)

\(\mathtt{a}\)

\(\mathtt{t}\)

\(\mathtt{o}\)

思路

很明显,如果循环节是 \(n\) 的因数,我们就不能出现重复。

因而,我们只需枚举所有数,找出第一个不是 \(n\) 的因数的数即可,这个数就可以作为循环节。

这样,即可让字符的种数最小。

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    int len = 1;
    while(n % len == 0) len ++;
    for(int i=1;i<=n;i++) cout << (char) ('a' + ((i - 1) % len));
    cout << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

什么诈骗题啊

E. Great Grids

题意

给定一个 \(n \times m\) 的字符矩阵,矩阵满足下面的条件:

  1. 字符为 \(A, B, C\) 中的其中一个;

  2. 每个 \(2 \times 2\) 连续子矩阵中,必须同时出现 \(A, B, C\)

  3. 相邻两个字符不能相同

现在,给定 \(k\) 个限制,每个限制给定两个坐标 \((x1, y1), (x2, y2)\),满足两点为 \(2 \times 2\) 连续子矩阵中的对角。

对于每个限制,要求这两点字符相同。

输出是否可以构造出这个矩阵。

思路

我们设 \(A = 0, B = 1, C = 2\)

观察/证明 1

对于下面的矩阵:

\(\mathtt{x\ \ y}\)

\(\mathtt{y\ \ z}\)

\((y - x)\mod 3 = p, (z - y)\mod 3 = q\),我们进行下面的化简:

\(=> (y - (3 - y - z))\mod 3 = p\)

\(=> (2y + z)\mod 3 = p\)

\(=> ((2y + z) - (z - y))\mod 3 = p - q\)

\(=> (3y) \bmod 3 = p - q\)

\(=> 0 = p - q\)

\(=> p = q\)

\(\mathtt{x\ \ y}\)

\(\mathtt{z\ \ x}\) 同理可证得。

那么,对于所有的 \(2 \times 2\) 连续子矩阵,由于两两相邻,等式具有传递性,也就是说,每两列左右元素差值在模 \(3\) 意义下相等。

行的差值同理可证得,模 \(3\) 意义下也是相等的。

观察/证明 2

对于下面的矩阵:

\(\mathtt{x\ \ y}\)

\(\mathtt{z\ \ x}\)

\((y - x)\mod 3 = p, (z - x)\mod 3 = q\),很显然 \(p \neq q\)

也就是说,"\" 对角线上相等,行列差值不同。

而对于下面的矩阵:

\(\mathtt{x\ \ y}\)

\(\mathtt{y\ \ z}\)

显然行列差值相同。

也就是说,"/" 对角线上相等,行列差值相同。

归纳

通过上述分析,我们可以将所有限制转化为对应的 相邻行差值 和 相邻列差值 是否相等。

更具体地说,我们将 所有相邻行和所有相邻列 的差值 这一"属性" 都视为图中的点,那么所有限制等价为两个点的值是否相同。

也就是等价为 图上两个点的颜色是否相同 的限制。

这就是 二分图 问题。那么,我们只需 \(\mathtt{dfs}\),判断涂色是否出现冲突即可。

时间复杂度:\(O(n + m + k)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 9, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<pii>> e(n + m + 1);
    while(k --){
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        e[min(x1, x2)].pb(n + min(y1, y2), y2 != y1 - 1);
        e[n + min(y1, y2)].pb(min(x1, x2), y2 != y1 - 1);
    }
    vector<int> color(n + m + 1, -1);
    bool f = true;
    auto dfs = [&](auto dfs, int x, int c) -> void{
        if(color[x] != -1 || !f) return;
        color[x] = c;
        for(auto [p, r] : e[x]){
            if(color[p] != -1){
                if((color[p] != color[x]) != r){
                    f = false;
                    cout << "NO\n";
                    return;
                }
                continue;
            }
            dfs(dfs, p, c ^ r);
            if(!f) return;
        }
    };
    for(int i=1;i<=n+m;i++){
        if(color[i] != -1) continue;
        dfs(dfs, i, 0);
        if(!f) return;
    }
    cout << "YES\n";
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

但凡往模三上去想