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