题目
题意
给出一个n个点,m条边组成的森林,有q组询问:
1.给出点x,求点x所在的树的直径
2.给出点x,y,要求将x,y所在的树之间连一条边并构成一棵新的树,满足这个新的树的直径最小
思路
1.求树的直径,并存在一个数组里
2.用并查集来动态合并+维护区域信息(包括同一颗树里的有着相同祖先的点的合并,不同树之间的合并)
3.假设length数组:对于每棵树的根节点x,length[x]=该树的直径长度
接下来对于每个询问2(如果给出的两点在同一颗树内则忽略),利用并查集找出两棵树的根节点x,y,并用并查集合并两棵树,
合并后树的直径为
max{[(length[x]+1)/2+(length[y]+1)/2+1,length[x],length[y]}
因为要想直径最短,我们选择加边的点一定要在直径上,因为其他的点走到直径还要一段距离,从而增长了路径。
那么直径就被选择的点分成了两段。因为我们要最小化较长的那一段,所以要让选择的点尽量靠近直径的中点。最后的答案就是 直径长度的一半向上取整
并且还要考虑原先两棵树本来就存在的直径,他们仨进行比较,最大的才是合并后的树的直径
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int inf=0x3f3f3f3f;
typedef long long ll;
struct Edge
{
int from,to;
};
vector<Edge>edges;
vector<int>G[maxn];
void add(int from,int to)
{
edges.push_back({from,to});
int m=edges.size();
G[from].push_back(m-1);
}
bool vis[maxn],vis2[maxn];
int pos,cnt;
int length[maxn],fa[maxn];
void dfs(int x,int len)
{
if(len>cnt)
{
pos=x;
cnt=len;
//cout<<1<<endl;
}
vis[x]=true;
for(int i=0;i<G[x].size();i++)
{
Edge e=edges[G[x][i]];
int u=e.to;
if(!vis[u])
{
dfs(u,len+1);
}
}
vis[x]=false;
}
int cal(int x)//找树的直径
{
cnt=-1;
dfs(x,0);
cnt=-1;
dfs(pos,0);
return cnt;
}
int find (int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
int xx=find(x),yy=find(y);
fa[xx]=yy;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
memset(vis,0,sizeof(false));
memset(vis2,0,sizeof(false));
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
merge(x,y);
}
for(int i=1;i<=n;i++)
{
if(fa[i]!=i||vis2[i])//因为有并查集的存在,所以只对树的根节点进行操作
continue;
vis2[i]=true;//标记
length[i]=cal(i);
}
while(q--)
{
int op,x,y;
cin>>op>>x;
if(op==1)
{
cout<<length[find(x)]<<endl;
continue;
}
else
{
cin>>y;
int xx=find(x),yy=find(y);
if(xx==yy)
continue;
int temp=((length[xx]+1)/2)+((length[yy]+1)/2)+1;
temp=max(temp,max(length[xx],length[yy]));
merge(x,y);//合并
length[yy]=temp;//更新
}
}
}