D55 树的直径 树形DP+并查集 P2195 HXY造公园
D55 树的直径 树形DP+并查集 P2195 HXY造公园_哔哩哔哩_bilibili
给出 n 个点,m 条边,构成若干颗树。q 次操作:
- 操作 1: 询问 x 所在树的直径;
- 操作 2: 在两颗树之间连一条边,使得新树的直径最短。
输出操作 1 的答案。
思路
合并两颗树,可以用并查集操作。
树的中心性质
- 当通过在两棵树 $x,y$ 间连一条边以合并为一棵树时,连接两棵树的中心可以使新树的直径最小。
- 新树的直径 $=max(x 的半径+y的半径+1,x 的直径,y的直径)$。
每次合并后,把新的直径保存到新树的树根上。
每次查询时,直接从树根上取即可。
当然,初始时,先枚举若干颗树的根,跑一下树形 DP,预处理出每颗树的直径存在根上。
时间复杂度为 $O(n)$。
// 树的直径 树形DP+并查集 O(n) #include<bits/stdc++.h> using namespace std; const int N=300010; int n,m,q; int fa[N],d[N],len,zj[N]; vector<int> e[N]; int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } void dfs(int x,int f){ //树形DP for(auto y:e[x])if(y!=f){ dfs(y,x); len=max(len,d[x]+1+d[y]); //保存直径 d[x]=max(d[x],d[y]+1); //x子树的最长链 } } int main(){ scanf("%d%d%d",&n,&m,&q); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1,x,y;i<=m;++i){ scanf("%d%d",&x,&y); e[x].push_back(y); e[y].push_back(x); fa[find(x)]=find(y); } for(int i=1;i<=n;++i)if(fa[i]==i){ //i是树根 len=0; dfs(i,0); zj[i]=len; //每颗树的直径存在根上 } for(int i=1,opt,x,y;i<=q;++i){ scanf("%d%d",&opt,&x); if(opt==1) printf("%d\n",zj[find(x)]); else{ scanf("%d",&y); x=find(x),y=find(y); if(x!=y){ fa[x]=y; //合并集合 zj[y]=max(((zj[x]+1)>>1)+((zj[y]+1)>>1)+1,max(zj[x],zj[y])); //新直径=max{(两个半径之和+1,1是连接边),x的直径,y的直径} } } } }
浙公网安备 33010602011771号