题目描述:
$zhx$有一个棵$n$个点的树,每条边有个权值。
定义一个连通块为一个点集与使这些点连通的所有边(这些点必须连通)。
定义一个连通块的权值为这个连通块的边权和(如果一个连通块只包含一个点,那么它的权值为$0$)。
$zhx$想找一个包含$1$号点的连通块送给他的妹子,所以他希望你求出包含$1$号点的所有连通块中权值第$k$小的连通块的权值。
题解:
非常裸的可持久化可并堆。
经典的$k$短路要求维护绕多远+终点,而它维护总边权+可选边集。
维护边集时需要可持久化可并堆。
还是经典操作,要么扔掉上一条边换上次优,要么在边集中找一个最优然后更新边集。
代码:
#include<queue> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 100050; const int MOD = 998244353; const int M = 50*N; typedef long long ll; template<typename T> inline void read(T&x) { T f = 1,c = 0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();} x = f*c; } int n,k,rt[N],wy[M]; ll ww[M]; struct edc { int x; ll d; edc(){} edc(int x,ll d):x(x),d(d){} friend bool operator < (edc a,edc b) { return a.d>b.d; } }tp; int tot,tw; struct node { int ls,rs,dis,tl; ll v; }p[M]; int merge1(int x,int y) { if(!x||!y)return x+y; if(p[x].v>p[y].v)swap(x,y); p[x].rs = merge1(p[x].rs,y); if(p[p[x].ls].dis<p[p[x].rs].dis)swap(p[x].ls,p[x].rs); p[x].dis=p[p[x].rs].dis+1; return x; } int merge(int x,int y) { if(!x||!y)return x+y; if(p[x].v>p[y].v)swap(x,y); int u = ++tot; p[u] = p[x],p[u].rs = merge(p[u].rs,y); if(p[p[u].ls].dis<p[p[u].rs].dis)swap(p[u].ls,p[u].rs); p[u].dis = p[p[u].rs].dis+1; return u; } priority_queue<edc>q; ll ans; int main() { freopen("tt.in","r",stdin); read(n),read(k); for(int f,w,i=1;i<n;i++) { read(f),read(w); p[++tot].dis=1,p[tot].tl=i+1,p[tot].v=w; rt[f] = merge1(rt[f],tot); } k--; tw=1; ww[tw] = p[rt[1]].v; wy[tw] = rt[1]; q.push(edc(1,ww[1])); while(k&&!q.empty()) { tp = q.top(); q.pop(); k--; int u = tp.x; ans = ww[u]; if(!k)break; int ls = p[wy[u]].ls,rs = p[wy[u]].rs; int tmp = merge(ls,rs); if(tmp) { tw++; ww[tw] = ww[u]-p[wy[u]].v+p[tmp].v; wy[tw] = tmp; q.push(edc(tw,ww[tw])); } tw++; wy[tw] = merge(tmp,rt[p[wy[u]].tl]); ww[tw] = ww[u]+p[wy[tw]].v; if(wy[tw])q.push(edc(tw,ww[tw])); } printf("%lld ",ans%MOD); return 0; }