几道有意思的数据结构题

「JOISC 2021 Day3」聚会 2

首先当 \(k\) 为奇数时答案显然为 \(1\)

否则考虑选择两个子树,把 \(k/2\) 个点放到其中一个子树,剩下的放到另一个中,那么要求就是这两个子树大小必须 \(\ge k/2\),期待值就是这两颗子树根节点的距离 \(+1\)

直接点分治,时间复杂度 \(O(n\log^2 n)\)。树状数组在 nkoj 会 TLE,我卡常换成了排序后直接处理,复杂度不变。

好像正解是高贵的 \(O(n\log n)\),但反正我卡过去了

#pragma GCC optimize("Ofast")
#include <cstdio>
#include <cstring>
#include <algorithm>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 65536, stdin), p1 == p2) ? EOF : *p1 ++)

inline int max(const int x, const int y) {return x > y ? x : y;}
char buf[65536], *p1, *p2;
inline int read() {
	char ch;
	int x = 0;
	while ((ch = gc) < 48);
	do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
	return x;
}

struct Edge {
	int to, nxt;
} e[400005];
int head[200005], sze[200005], sze2[200005], dep[200005], bel[200005], rt, fart, tot, now;
int ans[200005], a[200005], len, n;
bool vis[200005];
inline void AddEdge(int u, int v) {
	e[++ tot].to = v, e[tot].nxt = head[u], head[u] = tot;
}

void GetRoot(int u, int fa, int cnt) {
	sze2[u] = 1;
	int mx = 0;
	for (int i = head[u]; i; i = e[i].nxt)
		if (e[i].to != fa && !vis[e[i].to])
			GetRoot(e[i].to, u, cnt), mx = max(mx, sze2[e[i].to]), sze2[u] += sze2[e[i].to];
	mx = max(mx, cnt - sze2[u]);
	if (!rt || mx < now) rt = u, fart = fa, now = mx;
}
void getdep(int u, int fa) {
	if (u != rt) a[++ len] = u;
	sze[u] = 1;
	for (int i = head[u]; i; i = e[i].nxt)
		if (e[i].to != fa && !vis[e[i].to]) {
			dep[e[i].to] = dep[u] + 1;
			if (u == rt) bel[e[i].to] = e[i].to;
			else bel[e[i].to] = bel[u];
			getdep(e[i].to, u);
			sze[u] += sze[e[i].to];
		}
}

struct cmp {
	inline bool operator () (const int x, const int y) const {return sze[x] < sze[y];}
};
void calc(int u) {
	std::sort(a + 1, a + len + 1, cmp());
	int mx = -1e9, mxb = 0, semx = -1e9, semxb = 0;
	for (int i = len; i; -- i) {
		if (sze[rt] - sze[bel[a[i]]] >= sze[a[i]])
			ans[sze[a[i]] << 1] = max(ans[sze[a[i]] << 1], dep[a[i]]);
		if (bel[a[i]] != mxb) ans[sze[a[i]] << 1] = max(ans[sze[a[i]] << 1], dep[a[i]] + mx);
		else ans[sze[a[i]] << 1] = max(ans[sze[a[i]] << 1], dep[a[i]] + semx);
		if (!mxb || dep[a[i]] > mx) {
			if (bel[a[i]] != mxb) semx = mx, semxb = mxb;
			mx = dep[a[i]], mxb = bel[a[i]];
		}
		else if (bel[a[i]] != mxb && (!semxb || dep[a[i]] > semx))
			semx = dep[a[i]], semxb = bel[a[i]];
	}
}

void dfs(int u, int fa, int cnt) {
	rt = len = now = 0, GetRoot(u, -1, cnt), vis[u = rt] = true;
	dep[u] = 0, getdep(u, -1), calc(u);
	for (int i = head[u]; i; i = e[i].nxt)
		if (!vis[e[i].to]) dfs(e[i].to, u, e[i].to == fart ? cnt - sze2[u] : sze2[e[i].to]);
}

int main() {
	n = read();
	for (int i = 1, u, v; i < n; ++ i)
		u = read(), v = read(), AddEdge(u, v), AddEdge(v, u);
	dfs(1, -1, n);
	for (int i = n - 1; i; -- i) ans[i] = max(ans[i], ans[i + 1]);
	for (int i = 1; i <= n; ++ i) printf("%d\n", i & 1 ? 1 : ans[i] + 1);
	return 0;
}

「JOISC 2021 Day1」饮食区

把询问离线下来并拆开修改操作,看作在 \(L_i\) 时刻加入该操作,\(R_i\) 时刻删除该操作。把原本操作的时间看成下标。

那么对于当前需要处理的询问 \((a,b,t)\)\(t\) 是指这是在原始操作序列中的第 \(t\) 个),求出 \([1,t]\) 中的最大后缀和,显然这就是这个询问执行时实际上 \(a\) 队列剩下甜点的个数。

然后线段树上二分出它最早在哪个添加操作后被满足即可。复杂度是优雅的 \(O(n\log n)\)

#include <cstdio>
#include <vector>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 65536, stdin), p1 == p2) ? EOF : *p1 ++)

inline long long max(const long long x, const long long y) {return x > y ? x : y;}
char buf[65536], *p1, *p2;
inline int read() {
	char ch;
	int x = 0;
	while ((ch = gc) < 48);
	do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
	return x;
}
inline long long readll() {
	char ch;
	long long x = 0;
	while ((ch = gc) < 48);
	do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
	return x;
}

struct node {
	int l, r;
	long long suf, sum, sum2, sum3;
} tree[1000005];
struct tuple {
	long long x;
	int y, z;
};
struct tuple2 {
	long long x, y, z;
};
std::vector<int> vec1[250005], vec2[250005];
std::vector<tuple> vec3[250005];
int ans[250005], idx[250005], c[250005], k[250005], qstot;

void build(int p, int l, int r) {
	tree[p].l = l, tree[p].r = r;
	if (l != r) {
		build(p << 1, l, l + r >> 1);
		build(p << 1 | 1, (l + r >> 1) + 1, r);
	}
}
void update(int p, int x, int d) {
	if (tree[p].l == tree[p].r) {tree[p].suf += d, tree[p].sum += d, tree[p].sum2 = max(0, tree[p].sum3 += d); return;}
	int mid = tree[p].l + tree[p].r >> 1;
	if (x <= mid) update(p << 1, x, d);
	else update(p << 1 | 1, x, d);
	tree[p].suf = max(tree[p << 1 | 1].suf, tree[p << 1].suf + tree[p << 1 | 1].sum);
	tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
	tree[p].sum2 = tree[p << 1].sum2 + tree[p << 1 | 1].sum2;
}
tuple2 query(int p, int idx) {
	if (tree[p].r <= idx) return tuple2{tree[p].suf, tree[p].sum, tree[p].sum2};
	if (idx <= tree[p].l + tree[p].r >> 1) return query(p << 1, idx);
	tuple2 ret1 = tuple2{tree[p << 1].suf, tree[p << 1].sum, tree[p << 1].sum2};
	tuple2 ret2 = query(p << 1 | 1, idx);
	ret1.x += ret2.y, ret1.y = ret2.y = ret1.y + ret2.y, ret1.z = ret2.z = ret1.z + ret2.z;
	return ret2.x >= ret1.x ? ret2 : ret1;
}
int query2(int p, long long val) {
	if (tree[p].l == tree[p].r) return tree[p].l;
	return tree[p << 1].sum2 >= val ? query2(p << 1, val) : query2(p << 1 | 1, val - tree[p << 1].sum2);
}

int main() {
	int n = read(), m = read(), q = read();
	for (int i = 1; i <= q; ++ i) {
		int opt = read();
		if (opt == 1) {
			int l = read(), r = read();
			c[i] = read(), k[i] = read();
			vec1[l].push_back(i), vec2[r + 1].push_back(i);
		} else if (opt == 2) {
			int l = read(), r = read();
			k[i] = -read();
			vec1[l].push_back(i), vec2[r + 1].push_back(i);
		} else {
			int a = read();
			long long b = readll();
			vec3[a].push_back(tuple{b, ++ qstot, i});
		}
	}
	build(1, 1, q);
	for (int i = 1; i <= n; ++ i) {
		for (int j : vec1[i]) update(1, j, k[j]);
		for (int j : vec2[i]) update(1, j, -k[j]);
		for (tuple j : vec3[i]) {
			tuple2 tmp = query(1, j.z);
			if (tmp.x < j.x) continue;
			ans[j.y] = c[query2(1, j.x - tmp.x + tmp.z)];
		}
	}
	for (int i = 1; i <= qstot; ++ i) printf("%d\n", ans[i]);
	return 0;
}

「JOISC 2019 Day4」蛋糕拼接 3

怎么全是JOI.jpg

容易想到最优情况下就是把 \(C_i\) 从小到大排序围成一圈,美观程度为 \(\sum w-2(\max\{C\}-\min\{C\})\)

按照 \(C_i\) 排序后,相当于找 \(2(C_i-C_j)+cost(j,i)\) 的最大值,其中 \(cost(l,r)\) 表示 \([l,r]\) 内前 \(m\) 大数之和。

对于每个 \(i\)\(2(C_i-C_j)+cost(j,i)\) 的最优决策点 \(j\) 具有单调性,于是可以整体二分。

处理那个 \(cost(l,r)\) 的方法有一万种,有莫队套 set,莫队套可删堆,主席树,莫队套树状数组……

莫队套树状数组跑得最快,在经过一些卡常之后竟然以 38ms 的真·优势超过 nodgd 抢到 loj 榜一,泪目

#include <cstdio>
#include <queue>
#include <algorithm>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 65536, stdin), p1 == p2) ? EOF : *p1 ++)

char buf[65536], *p1, *p2;
inline int read() {
	char ch;
	int x = 0;
	while ((ch = gc) < 48);
	do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
	return x;
}

struct node {
	int w, h, rnk;
	inline bool operator < (const node a) const {return h < a.h;}
} a[200005];
long long ans = -1e18, c1[200005], sum;
int w[200005], c2[200005], n, m, L = 1, R = 0, N, lim;

inline void insert(int x, int d) {
	for (register int i = x; i <= N; i += (i & ~i + 1)) c1[i] += d, ++ c2[i];
}
inline void remove(int x, int d) {
	for (register int i = x; i <= N; i += (i & ~i + 1)) c1[i] -= d, -- c2[i];
}
inline long long query() {
	register int idx = 0, sum2 = 0;
	register long long sum1 = 0;
	for (register int i = lim, nxt; i >= 0; -- i) {
		nxt = idx | 1 << i;
		if (nxt <= N && sum2 + c2[nxt] <= m) sum1 += c1[idx = nxt], sum2 += c2[nxt];
	}
	return sum1 + (m - sum2) * w[idx + 1];
}

inline long long cost(int l, int r) {
	while (L > l) -- L, insert(a[L].rnk, a[L].w);
	while (R < r) ++ R, insert(a[R].rnk, a[R].w);
	while (L < l) remove(a[L].rnk, a[L].w), ++ L;
	while (R > r) remove(a[R].rnk, a[R].w), -- R;
	return query();
}

void solve(int l, int r, int x, int y) {
	int mid = l + r >> 1, k = 0;
	long long now = -1e18;
	for (int i = std::min(mid - m + 1, y); i >= x; -- i) {
		register long long tmp = 1ll * a[i].h - a[mid].h + cost(i, mid);
		if (!k || tmp > now) now = tmp, k = i;
	}
	ans = std::max(ans, now);
	if (l < mid) solve(l, mid - 1, x, k ? k : y);
	if (mid < r) solve(mid + 1, r, k ? k : x, y);
}

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++ i) a[i].w = w[i] = read(), a[i].h = read() << 1;
	std::sort(a + 1, a + n + 1);
	std::sort(w + 1, w + n + 1);
	N = std::unique(w + 1, w + n + 1) - w - 1;
	for (int i = 1; i <= n; ++ i) a[i].rnk = N - (std::lower_bound(w + 1, w + N + 1, a[i].w) - w) + 1;
	std::reverse(w + 1, w + N + 1);
	while (1 << lim + 1 <= N) ++ lim;
	solve(m, n, 1, n - m + 1);
	printf("%lld", ans);
	return 0;
}
posted @ 2022-04-10 09:46  zqs2020  阅读(80)  评论(0编辑  收藏  举报