C
我們用 \(1\sim K\) 的和減去出現在 \(1\sim K\) 中的數的和。
int n, k, a[N], res;
map<int, int> vis;
signed main() {
cin >> n >> k;
_for(i, 1, n) cin >> a[i];
res = k * (1 + k) / 2;
_for(i, 1, n) if (a[i] >= 1 && a[i] <= k && !vis[a[i]]) res -= a[i], vis[a[i]] = 1;
cout << res << endl;
}
D
這個題我腦溢血了,最開始從 \(2\) 維 dp 開始寫,寫了 10 min 發現假了,加了一維又調了好久好久。
\(dp_{i,j,k}\) 表示前 \(i\) 個數,存不存在有兩個字元相鄰 \(j=0/1\),當前這個位置翻轉還是不翻轉 \(k=0/1\)。
沒有腦血栓真建議別看我程式碼,我寫了 30 分鐘。
int n, b[N], dp[N][2][2];
char a[N];
char get(char c, int x) {
if (c == '0') {
if (x == 1) return '1';
return '0';
}
else {
if (x == 1) return '0';
return '1';
}
}
signed main() {
memset(dp, 0x3f, sizeof dp);
cin >> n >> (a + 1);
_for(i, 1, n) cin >> b[i];
dp[1][0][0] = 0;
dp[1][0][1] = b[1];
_for(i, 2, n) {
_for(j, 0, 1) {
_for(k, 0, 1) {
_for(w, 0, j) {
int t = 0;
if (a[i] == get(a[i - 1], k)) {
if (j == w) dp[i][j][1] = min(dp[i][j][1], dp[i - 1][w][k] + b[i]);
if (w == 0 && j == 1) dp[i][j][0] = min(dp[i][j][0], dp[i - 1][w][k]);
}
else {
if (w == 0 && j == 1) dp[i][j][1] = min(dp[i][j][1], dp[i - 1][w][k] + b[i]);
if (j == w) dp[i][j][0] = min(dp[i][j][0], dp[i - 1][w][k]);
}
}
}
}
}
cout << min(dp[n][1][0], dp[n][1][1]) << endl;
}
E
我們把操作倒著看。自己畫一下就知道,倒著來看操作的話,每操作一列,其實就代表之後所有的操作都不會影響到這一列,等價於把這一列刪了。
int n, m, c;
int t[N], a[N], x[N], vis[3][N], ans[N], res, cc;
signed main() {
cin >> n >> m >> c;
int tn = n, tm = m;
_for(i, 1, c) cin >> t[i] >> a[i] >> x[i];
_pfor(i, c, 1) {
if (vis[t[i]][a[i]]) continue;
vis[t[i]][a[i]] = 1;
if (t[i] == 1) ans[x[i]] += m, n--; // n--代表刪除這一行
else ans[x[i]] += n, m--;
}
_for(i, 1, N - 5) res += ans[i], cc += (ans[i] > 0);
if (res == tn * tm) cout << cc << endl;
else {
cout << cc + 1 << endl;
cout << 0 << ' ' << tn * tm - res << endl;
}
_for(i, 1, N - 5) {
if (ans[i]) cout << i << ' ' << ans[i] << endl;
}
}
F
肯定考慮二分 \(x\)。這就意味著對於 \(T\) 的所有字元,都要去對應 \(f(S,N)\) 中的對應字元 \(x\) 次。我們不用這樣考慮:如果當前 S 的指標匹配失敗了,就把指標跳到開頭再來匹配。我們直接把 \(S\) 複製 \(N\) 次,看成 \(len(S)\times N\) 長度的字串就行。可以避免大量分討。
還不明白?看程式碼吧。這樣複雜度是 \(O(n\times \log^2(len(S)\times N))\)。非常卡常數。
有一個卡常技巧,把 sum 陣列的小的那一維放前面去,直接快 \(0.5s\sim 1.5s\) 不等!我十分震撼!
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
//#define int long long
const int N = 1e5 + 5;
LL T;
int n, m, sum[27][N];
// sum_{c,i} 表示S中前 $i$ 個字元c出現了多少次
char a[N], b[N];
inline LL get(LL x, int c) {
return 1ll * sum[c][n] * (x / n) + sum[c][x % n]; // 代表複製後的S中前x個字元c出現了對少次
}
inline bool check(LL x) {
if (x == 0) return 1;
LL lst = 1;
_for(i, 1, m) {
int t = (int)(b[i] - 'a');
LL l = lst, r = T * n;
if (l > r) return 0;
while (l < r) {
LL mid = (l + r) >> 1;
if (get(mid, t) - get(lst - 1, t) >= x) r = mid;
else l = mid + 1;
}
if (get(l, t) - get(lst - 1, t) < x) return 0;
lst = l + 1;
}
return 1;
}
signed main() {
scanf("%lld%s%s", &T, (a + 1), (b + 1));
n = strlen(a + 1), m = strlen(b + 1);
_for(i, 1, n) {
int t = (int)(a[i] - 'a');
_for(j, 0, 25) {
if (t == j) sum[j][i] = sum[j][i - 1] + 1;
else sum[j][i] = sum[j][i - 1];
}
}
LL l = 0, r = T * n;
while (l < r) {
LL mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
if (!check(l)) puts("0");
else cout << l << endl;
}
G
賽時寫主席樹+樹狀陣列真的是腦抽死了,關鍵是還是假演算法。
我們考慮每一個數能在哪些區間中只出現一次。當前數字下標為 \(i\),往前看第一個和這個數字相同的下標為 \(l_i\),往後看第一個和這個數字相同的下標為 \(r_i\),那麼答案就是 \((i-l_i)\times (r_i-i)\)。
但是會算重。所以我們看成 \(n\) 個左上角為 \((l_i,i)\) ,右下角 \((i,r_i)\) 的矩形,求這些矩形的面積並。
#include <bits/stdc++.h>
using namespace std;
#define ls p << 1
#define rs p << 1 | 1
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long
const int N = 4e5 + 5;
int n, a[N], l[N], r[N], vis[N], b[N], n2, res;
struct edge {
int x, l, r, val;
}ed[N];
bool cmp(edge x, edge y) {
return x.x < y.x;
}
int tot;
struct tt {
int l, r, cnt, len;
}tree[N * 4];
void push_up(int p) {
if (tree[p].cnt) tree[p].len = b[tree[p].r + 1] - b[tree[p].l];
else tree[p].len = tree[ls].len + tree[rs].len;
}
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void modify(int p, int l, int r, int k) {
if (l <= tree[p].l && tree[p].r <= r) {
tree[p].cnt += k;
push_up(p);
return;
}
int mid = (tree[p].l + tree[p].r) >> 1;
if (l <= mid) modify(ls, l, r, k);
if (r > mid) modify(rs, l, r, k);
push_up(p);
}
int get_rk(int x) {
return lower_bound(b + 1, b + n2 + 1, x) - b;
}
signed main() {
cin >> n;
_for(i, 1, n) cin >> a[i];
_for(i, 1, n) l[i] = vis[a[i]], vis[a[i]] = i;
fill(vis, vis + N - 5, n + 1);
_pfor(i, n, 1) r[i] = vis[a[i]], vis[a[i]] = i;
_for(i, 1, n) b[++n2] = r[i], b[++n2] = i;
sort(b + 1, b + n2 + 1);
n2 = unique(b + 1, b + n2 + 1) - b - 1;
_for(i, 1, n) r[i] = lower_bound(b + 1, b + n2 + 1, r[i]) - b;
_for(i, 1, n) {
ed[++tot] = {l[i], get_rk(i), r[i], 1};
ed[++tot] = {i, get_rk(i), r[i], -1};
}
sort(ed + 1, ed + tot + 1, cmp);
build(1, 1, n2);
_for(i, 1, tot) {
modify(1, ed[i].l, ed[i].r - 1, ed[i].val);
if (i < tot) res += (ed[i + 1].x - ed[i].x) * tree[1].len;
}
cout << res << endl;
}