【CodeChef】Maximum and Minimum 题解(最小生成树 + 点/边分治 + 虚树 + Kruskal 重构树 + 线段树动态开点&线段树合并)

CC传送门

最小生成树 + 点/边分治 + 虚树 + Kruskal 重构树 + 线段树动态开点&线段树合并

(树上全家桶,实至名归。技巧性不高,但码量多板子难的那种。

Description


Solution

我们不难发现,简单点的较暴力的做法都会 \(\text{T}\) 飞掉,所以我们只好选择一个 \(\text{log}\) 做法。

首先,最直接最常规的一步,就是把两个连通图变为最小生成树,原因不赘述。

此时两点间路径具有了唯一性,故此题变为了树上路径问题。还是求路径权最大值,那不难想到是点/边分治(二者本质相同)。然后点分治好写,所以写点分治吧。

现在是对第一棵树进行点分治。对于当前重心 \(G\),按照点分治的套路,我们求出其子树内每个点 \(x\) 到它的路径中,边权最大值 \(mxd_x\)。那么此时,“经过 \(G\) 的、\(x\)\(y\) 之间的路径上边权的最大值”就是 \(\max{(mxd_x, mxd_y)}\)。显然,当前这个路径并不一定是两点间的最短路径,所以需要一个容斥去重。

如此我们得到了当前分治的树内的每一点对的 \(f(G_1, x, y)\)。如何求 \(f(G_2,x,y)\)?注意到此时涉及到的点不多且在第二棵树内是分散的,所以我们很自然就会使用虚树去解决。在第二棵树上对涉及到的点建虚树。然后我们按照边权从小到大加边,建 Kruskal 重构树。每次连接权为 \(w\) 的边,合并两棵子树 \(a\)\(b\),那么对于 \(a\) 中的某点 \(x\)\(b\) 中的某点 \(y\),显然有:\(f(G_2, x, y)=w\)。综上,对于点对 \(x,\ y\) 有:\(f(G_1,x,y)f(G_2,x,y)=\max{(mxd_x, mxd_y)} \times w\)

问题就是,如何快速统计当前合并时满足条件的 \(\max{(mxd_x, mxd_y)}\)?这个比较巧妙,我们可以在值域上动态开点线段树,初始时对每个点开一个线段树,在 \(mxd_x\) 那里标记。合并的时候两棵树对应的线段树合并即可。因为是在值域上开线段树,所以权值在 \([l,mid]\) 上的点 \(x\) 与权值在 \([mid+1,r]\) 上的点 \(y\) 合并时,它们第一棵树上的答案就是 \(mxd_y\)

每次统计完容斥一下即可。总时间复杂度 \(\mathcal{\text{O}}(n\log^2n)\)

总结一下:先对两个连通图建最小生成树,然后对第一棵树进行点分治,然后在第二棵树上建虚树,在虚树的 Kruskal 重构树动态开点线段树线段树合并统计答案,最后容斥一下得到答案。

Code

恶心就对了

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define per(i, a, b) for(int i = a; i >= b; --i)
#define go1(u) for(int i = hd1[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
#define go2(u) for(int i = hd2[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
const int maxn = 2e5 + 5, mod = 998244353; 
int n, m;
//---并查集 
int fas[maxn];
inline int fnds(int x){ return fas[x] == x ? x : fas[x] = fnds(fas[x]);}
inline void unions(int x, int y){ fas[fnds(x)] = fnds(y);}
//---最小生成树 
int hd1[maxn], hd2[maxn], cnte;
struct edge{
	int to, nxt, w;
}e[maxn << 3];
int U[maxn], V[maxn], W[maxn], I[maxn];
inline void adde(int *hd, int u, int v, int w){
	e[++cnte] = (edge){v, hd[u], w}, hd[u] = cnte;
}
inline bool cmpe(int x, int y){ return W[x] < W[y];}
inline void builde(int *hd){ int u, v, w;
	rep(i, 1, n) fas[i] = i;
	rep(i, 1, m) 
		scanf("%d%d%d", &U[i], &V[i], &W[i]), I[i] = i;
	sort(I + 1, I + m + 1, cmpe);
	rep(i, 1, m) if(fnds(u = U[I[i]]) != fnds(v = V[I[i]])){
		unions(u, v);
		adde(hd, u, v, w = W[I[i]]), adde(hd, v, u, w);
	}
}
//---预处理第二棵树 
int dfn2[maxn], dep2[maxn], tot2, fa2[maxn][25], mx2[maxn][25];
inline void dfs2(int x, int fa, int lst){
	dep2[x] = dep2[fa2[x][0] = fa] + 1;	
	dfn2[x] = ++tot2, mx2[x][0] = lst;
	rep(i, 1, 20) fa2[x][i] = fa2[fa2[x][i - 1]][i - 1],
		mx2[x][i] = max(mx2[x][i - 1], mx2[fa2[x][i - 1]][i - 1]);
	go2(x) if(v ^ fa) dfs2(v, x, e[i].w);
}
inline int lca(int x, int y){
	if(dep2[x] < dep2[y]) swap(x, y);
	per(i, 20, 0) 
		if(dep2[fa2[x][i]] >= dep2[y]) x = fa2[x][i];
	if(x == y) return x;
	per(i, 20, 0) if(fa2[x][i] != fa2[y][i])
		x = fa2[x][i], y = fa2[y][i];
	return fa2[x][0];
} 
//---建虚树 
set <pair<int, pair<int, int> > > ve; 
int av[maxn], cntv;
inline bool cmpv(int x, int y){ return dfn2[x] < dfn2[y];}
inline int upmxv(int fa, int x){
	int mx = 0;
	per(i, 20, 0) if(dep2[fa2[x][i]] >= dep2[fa])
		mx = max(mx, mx2[x][i]), x = fa2[x][i];
	return mx;
}
inline void buildv(int *a, int cnta){
	sort(a + 1, a + cnta + 1, cmpv);
	static int st[maxn], tp;
	ve.clear(); cntv = tp = 0;
	st[++tp] = av[++cntv] = a[1];
	rep(i, 2, cnta){
		int p = 0, l = lca(st[tp], a[i]);
		while(tp and dep2[st[tp]] >= dep2[l]){
			if(p) ve.insert({upmxv(st[tp], p), {st[tp], p}});
			p = st[tp--];
		}st[++tp] = l;
		if(p != l) ve.insert({upmxv(l, p), {l, p}}), av[++cntv] = l;
		if(l != a[i]) st[++tp] = a[i], av[++cntv] = a[i];
	} int p = 0;
	while(tp > 0){
		if(p) ve.insert({upmxv(st[tp], p), {st[tp], p}});
		p = st[tp--]; 
	}
}
//---线段树动态开点&合并
int sum, tott, rt[maxn];
struct tree{
	int ls, rs, nm, ct;
	tree(){ls = rs = nm = ct = 0;}
}t[maxn * 35];
#define lx t[x].ls
#define rx t[x].rs
inline void up(int x){
	t[x].nm = (t[lx].nm + t[rx].nm) % mod, t[x].ct = t[lx].ct + t[rx].ct;
}
inline void addt(int &x, int k, int L, int R){
	if(L > k or R < k) return;
	if(!x) x = ++tott, t[x] = tree();
	if(L == R){ t[x].nm = k, t[x].ct = 1; return;}
	int mid = (L + R) >> 1;
	addt(lx, k, L, mid), addt(rx, k, mid + 1, R), up(x);
}
inline int mrgt(int a, int b, int L, int R){
	if(!a or !b) return a | b;
	if(L == R){
		sum = (1ll * t[a].nm * t[b].ct + sum) % mod;
		t[a].ct += t[b].ct, t[a].nm = (t[a].nm + t[b].nm) % mod;
		return a;
	} int mid = (L + R) >> 1;
	(sum += 1ll * t[t[a].ls].ct * t[t[b].rs].nm % mod + 1ll * t[t[b].ls].ct * t[t[a].rs].nm % mod) %= mod;
	t[a].ls = mrgt(t[a].ls, t[b].ls, L, mid), t[a].rs = mrgt(t[a].rs, t[b].rs, mid + 1, R);
	return up(a), a;
}
inline int uniont(int a, int b, int vt){
	a = fnds(a), b = fnds(b), sum = 0;
	rt[b] = mrgt(rt[a], rt[b], 0, 1e8);
	sum = 1ll * sum * vt % mod, fas[a] = b;
	return sum; 
}
//---点分治
bool flg, vis[maxn];
int mxd[maxn], ad[maxn], cntd, siz[maxn], N, G;
inline void dfsd(int x, int fa, int lst){
	if(flg){ mxd[x] = lst;
		if(fa) mxd[x] = max(mxd[x], mxd[fa]);
		ad[++cntd] = x;
	} siz[x] = 1; bool flg2 = 0;
	go1(x) if(v ^ fa and !vis[v]){
		dfsd(v, x, e[i].w), 
		siz[x] += siz[v];
		if(siz[v] > N / 2) flg2 = 1;
	} if(!flg2 and (N - siz[x]) <= N / 2) G = x;
} 
inline int calc(int x, int lst){
	flg = 1, cntd = 0, dfsd(x, 0, lst), flg = 0;
	buildv(ad, cntd); 
	tott = 0; rep(i, 1, cntv) fas[av[i]] = av[i], rt[av[i]] = 0;
	rep(i, 1, cntd) addt(rt[ad[i]], mxd[ad[i]], 0, 1e8);
	int res = 0;
	while(ve.size()){
		int t1 = (ve.begin()->second).first, t2 = (ve.begin()->second).second, t3 = (ve.begin()->first);
		(res += uniont(t1, t2, t3)) %= mod;
		ve.erase(ve.begin());
	} return res;
}
inline int slv(int x){
	int res = calc(x, 0); vis[x] = 1;
	go1(x) if(!vis[v]){
		N = siz[v];
		res = (res - calc(v, e[i].w) + mod) % mod;
		res = (res + slv(G)) % mod;
	} return res; 
}
//---主函数 
signed main(){
	scanf("%lld%lld", &n, &m);
	builde(hd1), builde(hd2);
	dfs2(1, 0, 0), N = n, dfsd(1, 0, 0);
	printf("%lld\n", slv(G));
	return 0;
}

感谢阅读。

posted @ 2023-01-03 22:37  pldzy  阅读(85)  评论(0)    收藏  举报