Codeforces Round #767 (Div. 1) 题解 A – D2

本文链接

视频链接

A. Meximum Array

从左到右扫描, 维护每一个数字当前出现的次数和总的次数, 以此来 \(O(1)\) 地得到 \(x\) 是否存在于后缀中. 利用 std::set 维护 mex, 如果当前 mex 不存在于后缀, 即不会再变大, 则贪心地结束当前子串, 因为剩下的数字越多字典序越大(或不变).

#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long

const int maxn = 1e6 + 5;

int a[maxn], ap[maxn], sz[maxn];

void solve() {
    int n;
    cin >> n;
    inc(i, 0, n) sz[i] = ap[i] = 0;
    inc(i, 1, n) {
        cin >> a[i];
        sz[a[i]]++;
    }
    int mex = 0;
    set<int> s;
    vector<int> ans;
    inc(i, 1, n) {
        ap[a[i]]++;
        s.insert(a[i]);
        while (s.count(mex))
            mex++;
        if (ap[mex] == sz[mex]) {
            ans.push_back(mex);
            mex = 0;
            s.clear();
        }
    }
    if (s.size())
        ans.push_back(mex);
    cout << ans.size() << "\n";
    inc(i, 0, (int)ans.size() - 1) cout << ans[i] << " \n"[i + 1 == ans.size()];
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
}


B. Peculiar Movie Preferences

首先如果集合中有回文串, 则只取该串就可以得到"拼接"后的回文串. 特别地, 长度为 \(1\) 的串都是回文的.

此外, 如果存在两个串 s t, s == reverse(t), 那么只取这两个串也可以得到题意要求的.

如果不满足上述条件但仍然存在合法方案, 则第一个串和最后一个串长度一个是 \(2\) 另一个是 \(3\). 不妨设第一个串是 ab, 那么最后一个串一定得是 xba, x 可以是任何字符. 此时发现只取第一个串和最后一个串已经是一个合法方案.
剩下要做的事情就是, 枚举当前串为第一个串, 查询结尾为合法情况的串是否存在(hash 存储, 用上一题类似的方法查询后缀中是否存在某某).

#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long

const int maxn = 2e4 + 5;

int sz[maxn], ap[maxn];

int Hash(string s) {
    int r = 0;
    for (auto e : s) {
        r = r * 27 + e - 'a' + 1;
    }
    return r;
}

string rev(string s) {
    reverse(s.begin(), s.end());
    return s;
}

void solve() {
    int n;
    cin >> n;
    vector<string> v;
    int ok = 0;
    inc(i, 0, 20000) sz[i] = ap[i] = 0;
    inc(i, 1, n) {
        string s;
        cin >> s;
        if (s[0] == s.back())
            ok = 1;
        sz[Hash(s)]++;
        v.push_back(s);
    }
    inc(i, 0, n - 1) {
        ap[Hash(v[i])]++;
        int o = Hash(rev(v[i]));
        if (ap[o] < sz[o]) {
            ok = 1;
        }
        if (v[i].size() == 2) {
            string p = rev(v[i]);
            inc(j, 0, 25) {
                int u = Hash((char)(j + 'a') + p);
                if (ap[u] < sz[u]) {
                    ok = 1;
                }
            }
        } else if (v[i].size() == 3) {
            string p = rev(v[i]).substr(1);
            int u = Hash(p);
            if (ap[u] < sz[u]) {
                ok = 1;
            }
        }
    }
    if (ok)
        cout << "YES\n";
    else
        cout << "NO\n";
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
}


C. Grid Xor

如果能找到一个 \((i,j)\) 集合, 表示只取 \(a[i][j]'\) 时, 原矩阵每个元素的贡献都是 \(1\). 那么答案就是 \(a[i][j]\) 的异或和. 用 \(c[i][j]\) 表示 \(a[i][j]\) 是否取, \(1\)\(0\) 不取.

我们第一行随意赋值 \(c[1][i]\)(官方解法1是令\(c[1][i]=1\)). 然后从第二行开始从上到下扫描, 对于当前位置 \((i,j)\), 我们的目标是让 \(a[i-1][j]\) 的贡献为 \(1\), 即

\[c[i-2][j]\oplus c[i-1][j-1]\oplus c[i-1][j+1]\oplus c[i][j]=1 \]

这样便得到了 \(c[i][j]\).

不过现在还需要证明最后一行 \(a[n][i]\) 的贡献都是 \(1\).

考虑现在有一个合法方案 \(c[i][j]'\), 我们逐一比对求得的 \(c[1][i]\)\(c[1][i]'\), 如果不一样则修改 \(c[1][i]'\), 然后依次修改受影响的 \(c[i][j]'(i > 1)\), 整个过程保证 \(a[i][j]'\) 的贡献仍然为 \(1\). 可以发现最后两行一定是修改 \((n-1,n-i), (n-1)(n-i+2), (n,n-i+1)\), 则最后一行 \(a[i][j]'\) 的贡献不变, 仍为合法.

#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)

const int maxn = 1e3 + 5;

int a[maxn][maxn], c[maxn][maxn];

void solve() {
    int n;
    cin >> n;
    inc(i, 1, n) inc(j, 1, n) cin >> a[i][j];
    inc(i, 1, n) c[1][i] = rand() & 1;
    inc(i, 1, n) c[i][n + 1] = 0;
    inc(i, 2, n) inc(j, 1, n) {
        c[i][j] = c[i - 2][j] ^ c[i - 1][j - 1] ^ c[i - 1][j + 1] ^ 1;
    }
    int ans = 0;
    inc(i, 1, n) inc(j, 1, n) if (c[i][j]) ans ^= a[i][j];
    cout << ans << "\n";
}

int main() {
    srand(time(NULL));
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
}

另外还有构造图形的方法, 比如用一组两个相邻位置作为基本元素, 拼成一个 \(n\times n\) 的图形. Codeforces 题解讨论里还有一种黑白棋染色的方法也很巧妙.


D1. Game on Sum (Easy Version)

我们先把 \(k\) 视为单位一, 最后乘上就行.

定义 \(f(n, m)\) 为还剩 \(n\) 轮, 需要做 \(m\) 次加法的结果. 有 \(f(n, n) = n\), \(f(n, 0) = 0\). 另外当 \(n > m > 0\) 时:

\[f(n, m) = \min(-x+f(n-1,m), x+f(n-1,m-1)) (0\leq x\leq 1) \]

\[\max_{x}f(n,m)=\frac{f(n-1,m-1)+f(n-1,m)}{2} \]

至此, 我们可以 \(O(nm)\) 地 DP 解决 D1 的数据.

#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long

const int maxn = 2e3 + 5;
const int N = 2e3;
const int mod = 1e9 + 7;

int dp[maxn][maxn];

ll ksm(ll a, ll x) {
    ll r = 1;
    a %= mod;
    while (x) {
        if (x & 1)
            r = r * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return r;
}

ll inv(ll a) {
    return ksm(a, mod - 2);
}

void solve() {
    ll n, m, k;
    cin >> n >> m >> k;
    cout << dp[m][n] * k % mod << "\n";
}

int main() {
    ll i2 = inv(2);
    inc(i, 1, N) {
        dp[i][i] = i;
        inc(j, i + 1, N) {
            dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1]) % mod * i2 % mod;
        }
    }

    int T;
    cin >> T;
    while (T--) {
        solve();
    }
}


D2. Game on Sum (Hard Version)

沿用前面的 \(f(n,m)\) 的定义. 我们知道整个 DP 流程非常类似于杨辉三角, 只是多了个除以 \(2\), 这只要在考虑贡献时乘以 \(2^{-len}\). 也就是说 \(f(n,m)= \sum_{1\leq i\leq n} f(i,i) \times C \times 2^{-len}\), \(C\) 为 从 \((i+1,i)\)\((n,m)\) 的路径数(每次向右走一步或向右上走一步).

#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long

const int N = 1e6;
const int mod = 1e9 + 7;

ll fac[N + 5];

ll ksm(ll a, ll x) {
    ll r = 1;
    a %= mod;
    while (x) {
        if (x & 1)
            r = r * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return r;
}

ll inv(ll a) {
    return ksm(a, mod - 2);
}

ll C(int n, int m) {
    return fac[n] * inv(fac[m]) % mod * inv(fac[n - m]) % mod;
}

void solve() {
    ll n, m, k;
    cin >> n >> m >> k;
    ll ans = 0;
    if (n == m) {
        ans = n;
    } else {
        inc(i, 1, m) ans =
            (ans + i * C(n - i - 1, m - i) % mod * ksm(inv(2), n - i)) % mod;
    }
    cout << ans * k % mod << "\n";
}

int main() {
    fac[0] = 1;
    inc(i, 1, N) fac[i] = fac[i - 1] * i % mod;

    int T;
    cin >> T;
    while (T--) {
        solve();
    }
}

另一种做法, 按照 \(f(i,j)\) 求解方法补足 \(f(i,j)(i<j)\) 的值, 发现最后有 \(f(0,i)=2\times i\)(没有实际意义), 然后一样套用杨辉三角的方法.

void solve() {
    ll n, m, k;
    cin >> n >> m >> k;
    ll ans = 0;
    inc(i, 0, m) ans = (ans + i * 2 * C(n, m - i)) % mod;
    ans = ans * inv(ksm(2, n)) % mod;
    cout << ans * k % mod << "\n";
}
posted @ 2022-01-23 14:37  Linqi05  阅读(302)  评论(0编辑  收藏  举报