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

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;
}
posted @ 2024-03-25 21:31  Otue  阅读(6)  评论(0编辑  收藏  举报