【Codeforces 1120D】Power Tree
差分,最小生成树。
题目大意:
给定一棵树,每个叶子结点有一个正的权值。
现在可以选择若干个结点,然后对每个结点,选择一个值 $v$,则这个结点所在子树中所有结点的权值都加上 $v$。
选择每个结点,都有一个代价 $c_i$。要求选择的结点的代价之和最小,使得不管叶子结点的权值怎么样,都存在一种方法操作你选择的结点,使得所有叶子结点的权值变为 $0$。
输出最小的代价,并输出所有可能在最优方案中的点。
题解:
我们对叶子结点按访问的顺序编号,然后对于每个结点,它影响到的所有叶子结点的编号一定是一个连续的区间。
然后不管叶子的权值如何,我们可以对其进行差分,则一个影响 $[l,r]$ 的点,实际上是给 $l$ 的权值加 $v$,给 $r+1$ 的权值减 $v$。
那么要使权值任意情况都可以控制,我们需要令每个点都对应一个区间的左端点。
这些区间有一些特点,任意两个区间要么无交,要么存在包含关系。
不难发现如果我们对所有区间,连边 $l$ 和 $r+1$,那么最后选的边必须形成一个连通图。
所以求最小生成树即可。
至于输出方案,就是求所有可能在 MST 上的边。我们对边权相同的边同时考虑即可。
时间复杂度 $O(n\log n)$。
Code:
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
const int N=2e5+5;
struct edge{
int to,nxt;
}e[N<<1];
int n,head[N],cnt,val[N],tot,L[N],R[N],fa[N];
LL ans;
inline int find(int x){while(x!=fa[x])x=fa[x]=fa[fa[x]];return x;}
void dfs(int now,int pre){
L[now]=n+1,R[now]=0;
for(int i=head[now];i;i=e[i].nxt)if(e[i].to!=pre){
dfs(e[i].to,now);
L[now]=min(L[now],L[e[i].to]),R[now]=max(R[now],R[e[i].to]);
}
if(L[now]==n+1)L[now]=R[now]=++tot;
}
struct Eg{
int u,v,w,id;
inline bool operator<(const Eg&rhs)const{return w<rhs.w;}
}E[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",val+i);
for(int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
e[++cnt]=(edge){v,head[u]},head[u]=cnt;
e[++cnt]=(edge){u,head[v]},head[v]=cnt;
}
dfs(1,0);
for(int i=1;i<=n;++i)E[i]=(Eg){L[i],R[i]+1,val[i],i};
sort(E+1,E+n+1);
for(int i=1;i<=tot+1;++i)fa[i]=i;
vector<int>vec,vc;
vec.push_back(1);
for(int i=2;i<=n+1;++i){
if(i==n+1||E[i].w!=E[i-1].w){
for(int id:vec){
if(find(E[id].u)!=find(E[id].v))vc.push_back(E[id].id);
}
for(int id:vec){
int x=find(E[id].u),y=find(E[id].v);
if(x!=y)ans+=E[id].w,fa[y]=x;
}
vec.clear();
}
vec.push_back(i);
}
sort(vc.begin(),vc.end());
printf("%lld %d\n",ans,vc.size());
for(int i:vc)printf("%d ",i);
return 0;
}