CF455C Civilization 题解
思路
- 求树的直径,并存在一个数组里。
- 用并查集来动态合并加维护区域信息(包括同一颗树里的有着相同祖先的点的合并,不同树之间的合并)。
- 假设 \(length\) 数组:对于每棵树的根节点 \(x\),\(length_{x}=\) 该树的直径长度
接下来对于每个询问(如果给出的两点在同一颗树内则忽略),利用并查集找出两棵树的根节点 x,y,并用并查集合并两棵树,
合并后树的直径为 \(\max{[(length_{x}+1)\div2+(length_{y}+1)\div 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;
}
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;//更新
}
}
}