UNIQUE VISION Programming Contest 2024 Spring(AtCoder Beginner Contest 346)

Otue發表於2024-03-25

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;
}

相關文章