[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\n",ans);
}