[ABC 346] UNIQUE VISION Programming Contest 2024 Spring 题解

[ABC 346] UNIQUE VISION Programming Contest 2024 Spring 题解

A

模拟即可。

B

注意到子串一定有一部分是完整包含原串的,枚举散块的大小,然后判断是否剩下的可以组成若干原串即可。

string s = " wbwbwwbwbwbwwbwbwwbwbwbw";
for(int i = 1; i <= n; i ++) {
    for(int j = i; j <= n; j ++) {
        int x = w, y = b;
        for(int k = i; k <= j; k ++) {
            if(s[k] == 'w') x --;
            else y --;
        }
        if(x / 7 == y / 5 && x % 7 == 0 && y % 5 == 0) return cout << "Yes\n", 0;
    }
}

C

正难则反,统计区间内出现的整数和即可。

int ans = k * (k + 1) / 2;
for(int i = 1; i <= n; i ++) {
    if(a[i] <= k && !h.count(a[i])) ans -= a[i];
    h[a[i]] = 1;
}

D

考虑 DP,\(f_{i, 0/1, 0/1}\) 表示前 \(i\) 个位置,有没有相邻相同对,当前位置放0/1。

转移显然,basecase 要思考一下。

f[1][0][s[1] - '0'] = 0, f[1][0][(s[1] - '0') ^ 1] = c;
for(int i = 2; i <= n; i ++) {
    cin >> c;
    for(int a = 0; a < 2; a ++) {
        for(int b = 0; b < 2; b ++) {
            if(a == b) f[i][1][a] = min(f[i - 1][0][b] + (a != s[i] - '0') * c, f[i][1][a]);
            else {
                f[i][1][a] = min(f[i - 1][1][b] + (a != s[i] - '0') * c, f[i][1][a]);
                f[i][0][a] = min(f[i - 1][0][b] + (a != s[i] - '0') * c, f[i][0][a]);
            }
        }
    }
}
cout << min(f[n][1][0], f[n][1][1]) << '\n';

E

显然对于一行/列的所有操作,只有最后一次操作生效,但是对于一列染色,有可能中间的若干行会被行染色覆盖,所以可以这么计数:

首先默认没有行染色覆盖当前列,然后减去所有时间戳大于当前列的行的个数,这些行会覆盖当前列的一些方块,同理,还需要为每一个行染色加上时间戳小于当前行的列的个数,一开始全是 0 可以看作 \(n\) 次行染色,时间戳为 \(0\)

for(int i = 1; i <= n; i ++)
    r[i] = {0, 0};
for(int i = 1, op, x, cl; i <= q; i ++) {
    cin >> op >> x >> cl;
    if(op == 1) r[x] = {cl, i};
    else c[x] = {cl, i};
}
for(int i = 1; i <= n; i ++)
    cntr[r[i].y] ++;
for(int i = q; i >= 0; i --) cntr[i] += cntr[i + 1];
for(int i = 1; i <= m; i ++) {
    cnt[c[i].x] += n;
    cnt[c[i].x] -= cntr[c[i].y];
}
for(int i = 1; i <= m; i ++)
    cntc[c[i].y] ++;
for(int i = 1; i <= q; i ++) cntc[i] += cntc[i - 1];
for(int i = 1; i <= n; i ++)
    cnt[r[i].x] += cntc[r[i].y];

F

观察到答案具有二分性,考虑二分答案,然后贪心选择最靠前的匹配字符,正确性显然,难点在于怎么找到某一个位置往后的第 \(k\) 个相同字符的位置,和 B 一样,分讨就可以了,只是有点恶心。

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

// Problem: F - SSttrriinngg in StringString
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-03-23 20:54:15

#include <algorithm>
#include <iostream>
#include <queue>
#define int long long
using namespace std;
const int N = 2e5 + 10;

int n, ls, lt, cnt[N][30], pos[N];
string s, t;
vector<int> p[30];
int Next(int i, int c, int k) {
    if(!k) return i;
    int r = (i - 1) % ls + 1, d = (i - 1) / ls;
    if(cnt[r + 1][c] >= k) return i - r + p[c][pos[r] + k];
    k -= cnt[r + 1][c];
    i = (i + ls - 1) / ls * ls;
    if(k % cnt[1][c] == 0) return i + p[c].back() + (k / cnt[1][c] - 1) * ls;
    return p[c][k % cnt[1][c] - 1] + i + (k / cnt[1][c]) * ls;
}
bool check(int m) {
    for(int i = 1, now = 0; i < t.size(); i ++) {
        if(cnt[1][t[i] - 'a'] == 0) return 0;
        int r = (now - 1) % ls + 1;
        if(i == 1) {
            now = p[t[i] - 'a'][0];
        }
        else {
            if(r >= p[t[i] - 'a'].back()) now += ls - r + p[t[i] - 'a'][0];
            else now += *upper_bound(p[t[i] - 'a'].begin(), p[t[i] - 'a'].end(), r) - r;       
        }
        now = Next(now, t[i] - 'a', m - 1);
        if(now > n * ls) return 0;
    }
    return 1;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> s >> t;
    ls = s.size(), lt = t.size();
    s = " " + s, t = " " + t;
    for(int i = ls; i; i --) {
        for(int j = 0; j < 26; j ++) cnt[i][j] = cnt[i + 1][j];
        cnt[i][s[i] - 'a'] ++;
    }
    for(int i = 1; i <= ls; i ++) {
        p[s[i] - 'a'].push_back(i);
        pos[i] = p[s[i] - 'a'].size() - 1;
    }
    int l = 1, r = n * ls / lt + 1, ans = 0;
    while(l <= r) {
        int mid = l + r >> 1;
        if(check(mid)) l = mid + 1, ans = mid;
        else r = mid - 1;
    }
    cout << ans << '\n';

    return 0;
}

G

不难想到固定那个出现一次的位置,然后计算有多少区间覆盖了它,找前驱后继后,发现需要统计 \(\exists l_i\le L\le i \le R\le r_i\)\((L, R)\) 个数,而且同一个对只计算一次,所以可以联想到扫描线求矩形并。

// Problem: G - Alone
// Contest: AtCoder - UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-03-23 23:52:18

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int N = 4e5 + 10;

int n, a[N], idx;
vector<int> c[N];
struct owo {
    int l, r, op;
} ;
vector<owo> p[N];
struct qwq {
    int l, r, dat, tag, cnt;
} tr[N << 2];
qwq up(qwq u, qwq l, qwq r) {
    if(l.dat < r.dat) u.dat = l.dat, u.cnt = l.cnt;
    else if(l.dat > r.dat) u.dat = r.dat, u.cnt = r.cnt;
    else u.dat = l.dat, u.cnt = l.cnt + r.cnt;
    return u;
}
void align(int u, int v) {tr[u].dat += v; tr[u].tag += v;}
void down(int u) {
    if(tr[u].tag) 
        align(u << 1, tr[u].tag), align(u << 1 | 1, tr[u].tag), tr[u].tag = 0;
}
void build(int u, int l, int r) {
    int mid = l + r >> 1;
    tr[u] = {l, r, 0, 0, 0};
    if(l == r) return tr[u].cnt = 1, void();
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r), tr[u] = up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void update(int u, int ql, int qr, int v) {
    int l = tr[u].l, r = tr[u].r, mid = l + r >> 1;
    if(ql <= l && qr >= r) return align(u, v), void();
    down(u);
    if(ql <= mid) update(u << 1, ql, qr, v);
    if(qr > mid) update(u << 1 | 1, ql, qr, v);
    tr[u] = up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i], c[a[i]].push_back(i);
    for(int i = 1; i <= n; i ++) 
        if(c[i].size())
            for(int j = 0; j < c[i].size(); j ++) {
                int l = j ? c[i][j - 1] + 1 : 1, r = (j + 1 == c[i].size() ? n : c[i][j + 1] - 1), k = c[i][j];
                if(l <= k && k <= r) 
                    p[l].emplace_back(k, r, 1), p[k + 1].emplace_back(k, r, -1);
            }
    build(1, 1, n);
    long long ans = 0;
    for(int i = 1; i <= n; i ++) {
        for(auto [l, r, op] : p[i])
            update(1, l, r, op);
        ans += n - (!tr[1].dat ? tr[1].cnt : 0);
    }
    cout << ans << '\n';

    return 0;
}

总结

D 看到了一眼秒了,没有想好细节,调了 10min,由于这些时间的浪费,没有场切 F,更没有仔细想 G,总之切完 ABC 之后要稳心态,不要急。

posted @ 2024-03-24 00:44  MoyouSayuki  阅读(52)  评论(0编辑  收藏  举报
:name :name