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

MoyouSayuki發表於2024-03-24

[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 之後要穩心態,不要急。

相關文章