2023牛客暑期多校训练营2 DEFGHIK

比赛链接

D

题解

知识点:贪心。

首先,因为第一个人喜欢吃的可能会被后面的人选中,因此直接选最喜欢吃的可能会浪费机会。所以,我们考虑先看后面的人怎么选,就是倒着贪心,我们考虑证明。

假设当前剩下的菜集合为 A ,还剩下 k 个人没选,这 k 个人通过我们的策略选的菜的集合是 SA,k ,分类讨论:

  1. k=1 ,最优结果与 SA,1 一致。

  2. 假设 k=k 时的最优结果与 SA,k 一致。

    k=k+1 时,设 A 中除了 SA,k 之外倒数第 k+1 个人最喜欢的菜是 x ,对他的选择分类讨论:

    1. 若选择 x ,则结果与 SA,k+1 一致。
    2. 若选择 A 中除了 SA,kx 之外的菜,则后 k 个人结果不变,倒数第 k+1 个人亏。
    3. 若选择 SA,k 中的菜,那么后 k 个人有一个人的选择会改变:
      1. 若改变的那个人选择了 x ,则结果与 SA,k+1 一致。
      2. 若改变的那个人没选择 x ,则倒数第 k+1 个人亏。

    因此 k=k+1 时的最优解与 SA,k+1 一致,因此贪心策略是对的。

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

空间复杂度 O(nm+k)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; int a[2007][2007];int ans[2007];bool solve() {    int n, m, k;    cin >> n >> m >> k;    for (int i = 1;i <= n;i++)        for (int j = 1;j <= m;j++)            cin >> a[i][j];     vector<char> vis(m + 1);    for (int i = k;i >= 1;i--) {        int p = i % n;        if (!p) p = n;         int res = 0;        for (int j = 1;j <= m;j++) if (!vis[j] && a[p][res] < a[p][j]) res = j;        ans[i] = res;        vis[res] = 1;    }     sort(ans + 1, ans + k + 1);    for (int i = 1;i <= k;i++) cout << ans[i] << " \n"[i == k];    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

题解

知识点:数学。

显然 y2[x10k,(x+1)10k),k[0,18] 。注意 y21018 ,因此我们先保证 x10k1018

我们枚举 k ,得到 y 可能范围的左端点 l=x10k ,若 l2min(1018,(x+1)10k) ,那么 y=l

否则,无解。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; ll sqrt_fix(ll x, bool mode = false) {    ll v = sqrt(x);    while (v * v < x) ++v;    while (v * v > x) --v;    if (mode && v * v < x) v++;    return v;} bool solve() {    ll x;    cin >> x;    for (__int128_t i = x, j = x + 1;i <= (ll)1e18;i *= 10, j *= 10) {        ll l = sqrt_fix(i, 1);        if (l * l <= min(j - 1, (__int128_t)1e18)) {            cout << l << '\n';            return true;        }    }    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 << -1 << '\n';    }    return 0;}

F

题解

知识点:博弈论,二分图。

一个至关重要的模型,二分图博弈:

给出一张二分图和起点,轮流进行操作,每次操作只能选择与上一个被选的点相邻的点,且不能选择已经选择过的点,无法选点的人输掉。

关于上面的模型,结论是:若起始点一定在最大匹配上,那么先手赢,否则后手赢。

那和这道题有什么关系呢?

这道题我们把三元组 (r,g,b) 抽象成三维坐标的一个点,那么所有状态构成一个 n3 正方体,每个点可以走到相邻的点,那么这道题就变成了一个二分图博弈了,不过图是在三维上建的。接下来分类讨论:

  1. n 为偶数时,显然每个点都可以匹配到,因此所有点一定在最大匹配上,所以先手必胜。

  2. n 为奇数时,显然会有一个点在点较多一部中且没被匹配到,我们将证明这个点可以是较多一部中的任意一个点。

    首先,若点 (r0,g0,b0) 在较多一部中,那么 r0+b0+g0 是一个奇数。若 r0+g0+b0 是偶数,那么一定可以走一步,就可以与它匹配。

    因此, r0,g0,b0 中一定有至少一个奇数,我们不妨假设 r0 是奇数,显然 rr0 时的点都可以匹配到,所以我们可以只考虑 r=r0 这一层的情况。

    因为 g0+b0 一定是个偶数,我们发现,总能构造出一种方法使得 (r0,g0,b0) 不在其中。

    综上, r+b+g 为奇数时,后手必胜,否则先手必胜。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; bool solve() {    int n, r, g, b;    cin >> n >> r >> g >> b;    if ((n & 1) && (r + g + b & 1)) cout << "Bob" << '\n';    else cout << "Alice" << '\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;}

G

题解

知识点:manacher。

中心对称字符串和回文串可以理解为是同一个东西,不过匹配方式需要自定义。

这里需要用manacher算法,其中的细节是需要修改的。

接下来是关于本题的最关键的一个性质:若一个字符串能被分为若干个中心对称串,那么它一定能分为若干个任意前缀不是中心对称的中心对称串。

这个结论让我们在从左到右枚举对称中心的途中,对于一个好的前缀,可以往后截断一个最短的前缀中心对称串,而不影响答案。

若最后整个字符串能被截断完了,那么就是好的,否则不好。

证明:

假设一个中心对称串 S=AB ,其中 A 是最短的前缀对称串。

我们证明 |A||S|/2 。若 |A|>|S|/2 ,那么 B 的对称串 B ,有 A=BC ,使得 S=BCB 。 那么,其中 C 一定是中心对称串,而 CA 内,因此 A 的前缀一定有 C 的对称串也是中心对称的,因此 A 就不是最短的。

现在对于一个中心对称串 S 一定可以表达为 S=ABA ,其中 A,B,A 都是中心对称串,即一个中心对称串若含有更短的前缀中心对称串,那么就可以继续分下去,直到不包含任意前缀中心对称串。

这个结论同样适用于任何具有回文性质的字符串划分,都可以依次截断最短的前缀回文。

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; char rev[256]; void init() {    rev['o'] = 'o';    rev['s'] = 's';    rev['x'] = 'x';    rev['z'] = 'z';    rev['b'] = 'q';    rev['q'] = 'b';    rev['d'] = 'p';    rev['p'] = 'd';    rev['n'] = 'u';    rev['u'] = 'n';    rev['|'] = '|';} bool check(char a, char b) {    return b == rev[a];} bool manachar(const string &_s) {    string s = "$|";    for (int i = 1;i < _s.size();i++) {        s += _s[i];        s += "|";    }    s += "&";    int n = s.size() - 1;    vector<int> d(n + 1);     int start = 1;    int p = 0, r = 0;    for (int i = 1;i <= n - 1;i++) {        if (i <= r) d[i] = min(r - i + 1, d[2 * p - i]);        else d[i] = 0;        while (check(s[i - d[i]], s[i + d[i]])) d[i]++;        if (i + d[i] - 1 > r) {            p = i;            r = i + d[i] - 1;        }        if (i - d[i] + 1 <= start) {            start = i + d[i];            s[start - 1] = '$';            i = start - 1;        }    }    return start == n;}bool solve() {    string s;    cin >> s;    s = "?" + s;    cout << (manachar(s) ? "Yes" : "No") << '\n';    return true;} int main() {    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);    int t = 1;    cin >> t;    init();    while (t--) {        if (!solve()) cout << -1 << '\n';    }    return 0;}

H

题解

知识点:位运算,前缀和。

考虑快速求出操作区间对初始数字 x 的影响。

这里一个重要的转换是,操作A对 x 取反等价于 x1 ,因此考虑将答案表达为 kx+b,k{1,1} 的形式。

显然,只有操作A能改变 k ,因此 k 即为操作区间 [l,r] 中操作A的个数。

因为操作A能改变 b 的正负,所以任何操作对 b 的影响,与之后经历操作A的次数有关,我们分类讨论:

  1. 对于操作B,若其后有偶数个操作A,那么贡献为 1 ,否则为 1
  2. 对于操作A,若其后有偶数个操作A,那么贡献为 1 ,否则为 1

我们发现这个过程是能用前缀和维护的。具体来说,我们分别记录 k,b 在前缀 [1,i](i=1,,n) 操作下的结果。

现在我们询问 [l,r] 区间的结果。显然, kl;r=kl1kr 。对于 b 我们有 bl;r=brkl;rbl1 ,因为对于 [1,r] 的操作中,我们需要消除 [1,l1]b 的影响,因为这一段操作被之后 [l,r] 的操作A影响过了,因此我们需要对 bl1 修正,即 bl1kl;r

最终结果为 klkr(xbl1)+br

当然这个过程还能通过初等矩阵维护。

时间复杂度 O(n+q)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; int k[200007];int b[200007];int main() {    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);    int n, q;    cin >> n >> q;    string s;    cin >> s;    s = "?" + s;    k[0] = 1;    for (int i = 1;i <= n;i++) {        if (s[i] == 'A') {            k[i] = -k[i - 1];            b[i] = -b[i - 1] - 1;        }        else {            k[i] = k[i - 1];            b[i] = b[i - 1] + 1;        }    }     ll ans = 0;    while (q--) {        int l, r;        cin >> l >> r;        l = (ans ^ l) % n + 1;        r = (ans ^ r) % n + 1;        if (l > r) swap(l, r);         string t;        cin >> t;        ll x = 0, P = 1LL << t.size();        for (auto ch : t) (x <<= 1) |= ch == '1';         x -= b[l - 1];        x *= k[l - 1];        x *= k[r];        x += b[r];        ((x %= P) += P) %= P;         ans = x;        for (int i = t.size() - 1;i >= 0;i--) cout << ((x >> i) & 1);        cout << '\n';    }    return 0;}

I

题解

知识点:构造。

构造形如下方,注意字符顺序要严格:

xoxoxooxoxoxoxoxoxxoxoxo......

当且仅当 n=4k1,kN+m 是奇数时,这种构造需要将 ox 反转,变为:

oxoxoxoxoxxoxox

这是为了保证数量的严格。

可以证明 n,m 有一个为偶数时,这种构造一定是合法的。

当满足 n=4k1,kN+m 是奇数时,发现原来的构造会导致 ox 多一个。

时间复杂度 O(nm)

空间复杂度 O(nm)

代码

#include <bits/stdc++.h>using namespace std;using ll = long long; bool solve() {    int n, m;    cin >> n >> m;    bool f = (n / 2 & 1) && (n & 1) && (m & 1);    for (int i = 1;i <= n;i++) {        for (int j = 1;j <= m;j++) {            if (j & 1) cout << ((i / 2 & 1) ^ f ? "o" : "x");            else cout << ((i / 2 & 1) ^ f ? "x" : "o");        }        cout << '\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;}

K

题解

知识点:线性dp。

若没有向右移动,这道dp只需要多记录上一个位置有没有盖子即可。

但这道题可以向右移动,因此再多记录一维上一个位置若原来有盖子,有没有向右移动。

还有其他的状态方案,具体可以看代码。

我用的是方法一,上面讲的是方法二的,我觉得方法二更通用。

时间复杂度 O(n)

空间复杂度 O(n)

代码

方法一

#include <bits/stdc++.h>using namespace std;using ll = long long; int a[1000007];bool b[1000007];ll f[1000007][4]; // 第i个位置,没有/前面/自己/后面拿盖子int main() {    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);    int n;    cin >> n;    for (int i = 1;i <= n;i++) cin >> a[i];    for (int i = 1;i <= n;i++) cin >> b[i];     for (int i = 1;i <= n;i++) {        f[i][0] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2],f[i - 1][3] });        if (b[i - 1]) {            f[i][1] = max({ f[i - 1][1], f[i - 2][0],f[i - 2][1],f[i - 2][2] }) + a[i];        }        if (b[i]) {            f[i][2] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2] }) + a[i];        }        if (b[i + 1]) {            f[i][3] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2],f[i - 1][3] }) + a[i];        }    }    cout << max({ f[n][0],f[n][1],f[n][2] }) << '\n';    return 0;}

方法二

#include <bits/stdc++.h>using namespace std;using ll = long long; const int inf = 1e9;int a[1000007];bool b[1000007];ll f[1000007][2][2]; // 前i个位置,有无盖子,有无右移(btw:只有左移操作那只需要有无盖子)int main() {    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);    int n;    cin >> n;    for (int i = 1;i <= n;i++) cin >> a[i];    for (int i = 1;i <= n;i++) cin >> b[i];     f[0][0][0] = 0;    f[0][0][1] = -inf;    f[0][1][0] = -inf;    f[0][1][1] = -inf;    for (int i = 1;i <= n;i++) {        if (b[i]) {            f[i][0][0] = f[i - 1][0][0] + a[i - 1];            f[i][0][1] = max(f[i - 1][0][0], f[i - 1][1][0]);            f[i][1][0] = max(f[i - 1][0][0], f[i - 1][1][0]) + a[i];            f[i][1][1] = max(f[i - 1][0][1], f[i - 1][1][1]) + a[i];        }        else {            f[i][0][0] = max({ f[i - 1][0][0],f[i - 1][1][0] });            f[i][0][1] = -inf;            f[i][1][0] = max({ f[i - 1][0][1],f[i - 1][1][1] }) + a[i];            f[i][1][1] = -inf;        }    }    cout << max({ f[n][0][0],f[n][1][0] }) << '\n';    return 0;}

方法三

#include <bits/stdc++.h>using namespace std;using ll = long long; int a[1000007];bool b[1000007];ll f[1000007][3]; // 第i个位置,有给前面,没有不管/有自己用,没有从前面拿/一定有且给后面int main() {    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);    int n;    cin >> n;    for (int i = 1;i <= n;i++) cin >> a[i];    for (int i = 1;i <= n;i++) cin >> b[i];     for (int i = 1;i <= n;i++) {        if (b[i]) {            f[i][0] = f[i - 1][0] + a[i - 1];            f[i][1] = max(f[i - 1][0], f[i - 1][1]) + a[i];            f[i][2] = max({ f[i - 1][0], f[i - 1][1], f[i - 1][2] }) + a[i + 1];        }        else {            f[i][0] = max(f[i - 1][0], f[i - 1][1]);            f[i][1] = f[i - 1][2];        }    }    cout << max(f[n][0], f[n][1]) << '\n';    return 0;}

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYVFLeSw' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片