简要题意
给定一棵 \(n\) 个节点的有根树,树根为 \(1\) 号节点,每个结点有一个权值 \(a_i(|a_i|\leq 10^9)\) ,求包含 \(1\) 的前 \(k\) 小的连通块的权值。
简要题解
前置内容
平面图转对偶图
平面图 :任一两条边不在非顶点除相交。
对偶图 :将平面图的每一个封闭的平面,向与其相邻的的平面连边,在本题树形图的对偶图边权就是子树的权值和。
举个例子:
- 性质: 平面图的最小割等于其对偶图的最短路,最大割最长路。
可持久化可并堆求 \(k\) 短路
可持久化可并堆:是一个支持快速合并的堆,合并部分代码如下:
inline int Merge(int x,int y) {
if(!x || !y) return x+y;
if(tre[x].len>tre[y].len) swap(x,y);
int k=++idx; tre[k]=tre[x];
if(rand()&1) tre[k].lc=Merge(tre[k].lc,y);
else tre[k].rc=Merge(tre[k].rc,y);
return k;
}
具体算法流程如下:
- 我们首先从 \(t\) 向 \(s\) 跑一遍最短路,把最短路树建出来,最短路树就是以 \(t\) 为根节点,每个结点到根节点的路径表示着原图上该结点到根节点的一条最短路。
- 然后我们考虑将最短路上的一些边进行替换或者增加。
此时我们考虑维护一些点对:
\[(a_1,a_2),(a_3,a_4).......(a_{m-1},a_m) \]表示的是原图上一条路径:
\(s-a_1\) 走最短路树上的路径,经过 \((a_1,a_2)\) 这条边,\(a_2-a_3\) 走最短路树上的路径 ...... 经过 \((a_{m-1},a_m)\) 这条边, \(a_m-t\) 按照最短路树上的路径走。
如下图所示:
- 我们考虑怎样用一条路径拓展出一条稍微比其劣一点点的路径,我们只考虑改变所维护的最后一条边(因为前面如果要改变就会在之前所枚举)。那么只有两种拓展的情况。
- 把当前维护的最后一条边给扬了换成一条略大一点的边。
- 在路径后面新加入一条边。
我们用可持久化可并堆来维护。堆内的每一个节点就表示原图中的一条不在最短路树上的边。原图中每一个点的堆内就维护所有从其及其最短路树上的祖先延伸出去的非树边,一条从 \(x->y\) 权值为 \(e_i\) 的边,定义其新的边权 \(\Delta e=dist[y]-dist[x]+e_i\) ,也就是用这条边替换原最短路上的边造成的最短路长度的变化量。
我们当前维护的最后一条边表示为 $(len,node) $ ,表示这一条路径在这条边之前的路径长度为 \(len\) ,可并堆内 \(node\) 结点所维护的就是当前这一条边。
那么对于第一种情况,就是要查这条边在其所在堆内的后继,直接将左右儿子合并,那么堆顶元素也就是所要找的后继。
对于第二种情况,就是原先 \(a_m-t\) 这段路径是沿着最短路树走,现在我们把其中一段替换为一条非树边 \((a_{m+1},a_{m+2})\) 。那么相当于我们接受当前维护的最后一条边,到达 \(a_m\) ,在 \(a_m\) 的可并堆内找到最小的一条边(也就是堆顶),并变成维护 \((a_{m+1},a_{m+2})\) 这条边。
对于拓展出来的两种情况,把它丢到优先队列里即可,然后就做完了!。
建树的时间复杂度是 \(o(m\log m)\) ,求 \(k\) 短路的复杂度是 \(k\log k\) 。
空间复杂度是 \(o((k+m)\log m)\) 。
关于本题
本题要求前 \(k\) 小的包含 \(1\) 结点的连通块的权值和,那么可以转化为求解一个前 \(k\) 大割,将权值取反就是求前 \(k\) 小割,因此我们把原图转化为对偶图,在最短路上求解前 \(k\) 短路,再加上所有结点的权值和就是第 \(k\) 小的连通块。
关于样例的解释:
code
点击查看代码
#include<bits/stdc++.h>
#define mem(a) memset(a,0,sizeof(a))
#define LL long long
using namespace std;
const int N=1e5+5;
const LL inf=0x3f3f3f3f3f3f3f3f;
inline int read() {
int x=0,w=0; char ch=getchar();
while(!isdigit(ch)) w|=(ch=='-'), ch=getchar();
while(isdigit(ch)) x=x*10+(ch^48), ch=getchar();
return w?-x:x;
}
int n,k,u,v,m,lf,id[N],fa[N],d[N],leaf[N],a[N];
int hd[N],to[N<<1],ne[N<<1],tc=1;
LL s[N],dist[N],e[N<<4];
int idx,rt[N];
struct Lefttree { int lc,rc,to; LL len; }tre[N<<5];
struct Val {
int node; LL len;
bool friend operator<(Val x,Val y) { return x.len+tre[x.node].len>y.len+tre[y.node].len; }
}now;
priority_queue<Val> Q;
inline void Add(int,int,LL);
inline void Dfs(int);
inline void Col(int,int,int);
inline int Merge(int,int);
int main() {
srand(time(NULL));
n=read(); k=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++) u=read(), v=read(), Add(u,v,0LL), Add(v,u,0LL);
Dfs(1); tc=1; mem(hd);
Col(1,leaf[1],++m);
for(int i=1;i<lf;i++) Col(leaf[i],leaf[i+1],++m);
Col(leaf[lf],1,++m);
for(int i=1;i<m;i++) Add(i,i+1,0);
for(int x=m-1,eg;x>0;x--) {
dist[x]=inf; eg=1;
for(int i=hd[x],y;i;i=ne[i])
if(dist[y=to[i]]+e[i]<dist[x]) eg=i, dist[x]=dist[y]+e[i];
rt[x]=rt[to[eg]];
for(int i=hd[x],y;i;i=ne[i]) if(i^eg) {
y=to[i];
tre[++idx]=Lefttree{0,0,y,dist[y]-dist[x]+e[i]};
rt[x]=Merge(rt[x],idx);
}
}
printf("%lld\n",dist[1]+s[1]); --k;
now.len=0; now.node=rt[1];
if(now.node) Q.push(now);
int lst;
while(k-- && Q.size()) {
now=Q.top(); Q.pop();
printf("%lld\n",dist[1]+s[1]+now.len+tre[now.node].len);
lst=now.node;
now.node=Merge(tre[lst].lc,tre[lst].rc);
if(now.node) Q.push(now);
now.node=rt[tre[lst].to]; now.len+=tre[lst].len;
if(now.node) Q.push(now);
}
return 0;
}
inline void Add(int x,int y,LL z) {
to[++tc]=y; ne[tc]=hd[x]; hd[x]=tc; e[tc]=z;
}
inline void Dfs(int x) {
bool isleaf=1; s[x]=a[x];
for(int i=hd[x],y;i;i=ne[i]) if((y=to[i])!=fa[x]) {
fa[y]=x; d[y]=d[x]+1; isleaf=0;
Dfs(y);
s[x]+=s[y];
}
if(isleaf) leaf[++lf]=x;
}
inline void Col(int x,int y,int z) {
while(x^y)
if(d[x]<d[y]) id[y]=z, y=fa[y];
else Add(id[x],z,-s[x]), x=fa[x];
}
inline int Merge(int x,int y) {
if(!x || !y) return x+y;
if(tre[x].len>tre[y].len) swap(x,y);
int k=++idx; tre[k]=tre[x];
if(rand()&1) tre[k].lc=Merge(tre[k].lc,y);
else tre[k].rc=Merge(tre[k].rc,y);
return k;
}