[IOI2011]Race(树上启发式合并)

题意:

给一棵树,每条边有权,求一条简单路径,使得权值和等于k且边权最小。

题解:

DSU on Tree裸题,合并的时候套个map就行。注意慎用Splay,Splay常数巨大,并且感觉好像复杂度并不是均摊的。


#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+100;
typedef long long ll;
map<ll,int> mp;
vector<pair<int,int> > g[maxn];
int n,m;
int L[maxn],R[maxn],son[maxn],tol;
ll dep[maxn];
int ans=1e9,h[maxn],id[maxn];
void dfs1 (int u,int f) {
	L[u]=++tol;
	id[tol]=u;
	for (pair<int,int> it:g[u]) {
		int v=it.first;
		if (v==f) continue;
		dep[v]=dep[u]+it.second;
		h[v]=h[u]+1;
		dfs1(v,u);
		if (!son[u]) son[u]=v;
		else if (R[v]-L[v]+1>R[son[u]]-L[son[u]]+1) son[u]=v;
	}
	R[u]=tol;
}
void cal (int u,int f) {
	if (mp.count(dep[u]+m))ans=min(ans,mp[dep[u]+m]-h[u]);
	if (!mp.count(dep[u]))mp[dep[u]]=h[u];
	else mp[dep[u]]=min(mp[dep[u]],h[u]);
	//splay内存储重儿子的所有节点路径 
	for (pair<int,int> it:g[u]) {
		int v=it.first;
		if (v==son[u]) {
			continue;
		} 
		if (v==f) continue;
		for (int j=L[v];j<=R[v];j++) {
			if (mp.count(1ll*m+2ll*dep[u]-dep[id[j]]))ans=min(ans,mp[1ll*m+2ll*dep[u]-dep[id[j]]]+h[id[j]]-2*h[u]);
		}
		for (int j=L[v];j<=R[v];j++) {
			//ins(dep[id[j]],h[id[j]]);
			if (!mp.count(dep[id[j]]))mp[dep[id[j]]]=h[id[j]];
			else mp[dep[id[j]]]=min(mp[dep[id[j]]],h[id[j]]);
		}
	}
}
void dfs (int u,int f,int kp) {
	for (pair<int,int> it:g[u]) {
		int v=it.first;
		if (v==f) continue;
		if (v==son[u]) continue;
		dfs(v,u,0);
	}
	if (son[u]) dfs(son[u],u,1);
	cal(u,f);
	if (!kp) mp.clear();
}
int main () {
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;i++) {
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		u++;v++;
		g[u].push_back(make_pair(v,w));
		g[v].push_back(make_pair(u,w));
	}

	dfs1(1,0);
	dfs(1,0,1);
	if (ans>n) ans=-1;
	printf("%d
",ans);
}
原文地址:https://www.cnblogs.com/zhanglichen/p/15072050.html