NFLSOJ #12381 -「NOIP2021模拟赛0825华二」量筒(Kruskal 重构树)

题面传送门

莫名感觉这个官方题解写的有些简略啊……所以看不懂官方题解的不妨来康康这篇题解?(大雾

首先不难发现一个性质,那就是如果我们求出了原图的最小生成树,那么不在最小生成树上的边肯定是不会被水经过的,换句话说,如果把这些不在最小生成树上的边去掉,那么答案不会改变,因为如果这条边中有一个端点水位达到了这条边的高度,那么显然这两个点此时已经连通了,这个结合 \(h_i\) 互不相同的性质可以轻松证明。因此如果此时再往其中一个端点注水,即使没有这条边,那么水依旧可以流到另一个端点,此时这条边就是可有可无的。

因此问题就由图上的问题转化为树上的问题。我们先以 \(1\) 为根做一遍 DFS,对于每组询问我们考虑二分答案 \(mid\),然后我们求出点 \(t\) 经过边权 \(<mid\) 的点可以到达哪些点,方便起见,我们记这样的点组成的集合为 \(S\)\(x=|S|\),这些点中深度最小的点为 \(p\),这个可以通过 Kruskal 重构树 + 倍增求出。那么考虑往 \(1\) 号点中注水提高水位的过程是什么样的,显然我们会先注水直到 \(p\) 点中有水,然后再往 \(1\) 中注水直到 \(t\) 中水面达到 \(mid\) 的过程,显然是 \(S\) 中的点的水面同时增加 \(mid\) 的过程,而这段过程之前有水的那些量筒,由于这些点与 \(S\) 之间的点有着一个高度 \(\ge mid\) 的边连着,并且现在水已经流到 \(S\) 中了,因此如果你现在再往 \(1\) 号量筒中注水,水就会顺着管道流到没有水的地方,也就是 \(S\) 中。如果我们记 \(f_p\) 表示最少需要往 \(1\) 号量筒中注入多少水才能使 \(p\) 中有水,那么我们最少需要向 \(1\) 号桶中注入 \(f_p+x·mid\) 的水才能使得 \(t\) 中的水量达到 \(mid\)。如果我们预处理 \(f_p\) 之后,那我们只需将 \(f_p+x·mid\)\(a\) 比较大小即可知道水面能否达到 \(mid\)

于是问题转化为如何求 \(f_p\)。我们考虑做一遍 DFS,求解一个点 \(x\)\(f_x\) 时,我们还是考虑往 \(1\) 号点中注水知道 \(x\) 中有水的过程是什么样的。我们设 \(x\) 的父亲为 \(fa\)\(x\) 与其父亲之间的边的高度为 \(w\),那么我们考虑找到 \(fa\) 经过边权 \(<w\) 的边能够到达的点集 \(S\)\(S\) 中深度最小的点为 \(p\),那么仿照上面二分过程的推理,往 \(1\) 号点中注水直到 \(x\) 中有水的过程也可分为两步,一是注水直到 \(p\) 中有水,二是往 \(S\) 中的点注水直到 \(S\) 中所有点的水面都达到了 \(w\),此时只要再往 \(1\) 号量筒中注入 \(10^{-114514191981019260817998244353}\text{cm}^3\) 的水后,这些水都能流到 \(x\),因此我们有 \(f_x=f_p+|S|·w\),这个同样在 Kruskal 重构树上倍增即可。

时间复杂度 \(\Theta(n\log^2n)\),应该可以不用二分,直接在 Kruskal 重构树上倍增做到 \(n\log n\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a, 0, sizeof(a))
#define fill1(a) memset(a, -1, sizeof(a))
#define fillbig(a) memset(a, 63, sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
#define mt make_tuple
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
template <typename T1,typename T2> void chkmin(T1 &x, T2 y){
	if (x > y) x = y;
}
template <typename T1,typename T2> void chkmax(T1 &x, T2 y){
	if (x < y) x = y;
}
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef long double ld;
namespace fastio {
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE], *p1 = rbuf, *p2 = rbuf, wbuf[FILE_SIZE], *p3 = wbuf;
	inline char getc() {
		return p1 == p2 && (p2 = (p1 = rbuf) + fread(rbuf, 1, FILE_SIZE, stdin), p1 == p2) ? -1: *p1++;
	}
	inline void putc(char x) {(*p3++ = x);}
	template <typename T> void read(T &x) {
		x = 0; char c = getchar(); T neg = 0;
		while (!isdigit(c)) neg |= !(c ^ '-'), c = getchar();
		while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
		if (neg) x = (~x) + 1;
	}
	template <typename T> void recursive_print(T x) {
		if (!x) return;
		recursive_print (x / 10);
		putc (x % 10 ^ 48);
	}
	template <typename T> void print(T x) {
		if (!x) putc('0');
		if (x<0) putc('-'), x = -x;
		recursive_print(x);
	}
	template <typename T> void print(T x,char c) {print(x); putc(c);}
	void print_final() {fwrite(wbuf, 1, p3-wbuf, stdout);}
}
const int MAXN = 1e5;
const int LOG_N = 18;
const int INF = 0x3f3f3f3f;
int n, m, qu;
int hd[MAXN + 5], to[MAXN * 2 + 5], nxt[MAXN * 2 + 5], val[MAXN * 2 + 5], ec = 0;
void adde(int u, int v, int w) {to[++ec] = v; val[ec] = w; nxt[ec] = hd[u]; hd[u] = ec;}
int _hd[MAXN * 2 + 5], _to[MAXN * 2 + 5], _nxt[MAXN * 2 + 5], _ec = 0;
void _adde(int u, int v) {_to[++_ec] = v; _nxt[ec] = _hd[u]; _hd[u] = _ec;}
struct edge {
	int u, v, w;
	bool operator <(const edge &rhs) const {
		return w < rhs.w;
	}
} e[MAXN + 5];
int f[MAXN * 2 + 5], v[MAXN * 2 + 5], ncnt;
int find(int x) {return (!f[x]) ? x : f[x] = find(f[x]);}
int dep[MAXN + 5], faw[MAXN + 5];
void dfs1(int x, int f) {
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e], z = val[e]; if (y == f) continue;
		faw[y] = z; dep[y] = dep[x] + 1;
		dfs1(y, x);
	}
}
int siz[MAXN * 2 + 5], fa[MAXN * 2 + 5][LOG_N + 2];
pii mndep[MAXN * 2 + 5];
void dfs2(int x, int f) {
	fa[x][0] = f;
	if (x <= n) siz[x] = 1, mndep[x] = mp(dep[x], x);
	else siz[x] = 0, mndep[x] = mp(INF, 0);
	for (int e = _hd[x]; e; e = _nxt[e]) {
		int y = _to[e]; dfs2(y, x);
		siz[x] += siz[y]; chkmin(mndep[x], mndep[y]);
	}
}
ll dp[MAXN + 5];
void dfs3(int x, int f) {
	if (x != 1) {
		int tmp = f;
		for (int i = LOG_N; ~i; i--) {
			if (fa[tmp][i] && v[fa[tmp][i]] < faw[x]) tmp = fa[tmp][i];
		}
		dp[x] = dp[mndep[tmp].se] + 1ll * siz[tmp] * faw[x];
	}
	for (int e = hd[x]; e; e = nxt[e]) if (to[e] != f)
		dfs3(to[e], x);
}
int main() {
	freopen("cylinder.in", "r", stdin);
	freopen("cylinder.out", "w", stdout);
	scanf("%d%d%d", &n, &m, &qu); ncnt = n;
	for (int i = 1; i <= n; i++) siz[i] = 1;
	for (int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
	sort(e + 1, e + m + 1);
	for (int i = 1; i <= m; i++) {
		int fu = find(e[i].u), fv = find(e[i].v);
		if (fu == fv) continue;
		adde(e[i].u, e[i].v, e[i].w);
		adde(e[i].v, e[i].u, e[i].w);
		f[fu] = f[fv] = ++ncnt; v[ncnt] = e[i].w;
		_adde(ncnt, fu); _adde(ncnt, fv);
	}
	//dfs on original tree
	dfs1(1, 0);
	//dfs on kruskal rebuilt tree
	dfs2(ncnt, 0);
	for (int i = 1; i <= LOG_N; i++) for (int j = 1; j <= ncnt; j++)
		fa[j][i] = fa[fa[j][i - 1]][i - 1];
	//calculating DP
	dfs3(1, 0);
//	for (int i = 1; i <= n; i++) printf("%d %lld\n", i, dp[i]);
	while (qu--) {
		int a, t; scanf("%d%d", &a, &t);
		int l = 0, r = 1e9, p = 0;
		while (l <= r) {
			int mid = l + r >> 1, tmp = t;
			for (int i = LOG_N; ~i; i--) {
				if (fa[tmp][i] && v[fa[tmp][i]] < mid)
					tmp = fa[tmp][i];
			}
//			printf("%d %d %d %d\n", mid, tmp, mndep[tmp].se, siz[tmp]);
			if (dp[mndep[tmp].se] + 1ll * mid * siz[tmp] <= a)
				p = mid, l = mid + 1;
			else r = mid - 1;
		}
		printf("%d\n", p);
	}
	return 0;
}
/*
4 3 100
1 2 100
2 3 50
3 4 80
100 2
102 2
155 3
210 2
210 1
149 3
259 4
261 4

4 3 100
1 2 100
1 3 80
1 4 50
102 2
*/
posted @ 2021-11-16 22:58  tzc_wk  阅读(33)  评论(0)    收藏  举报