CF1804F Approximate Diameter 题解

前言

在学校机房被学长推荐了这道题,听完正解后惊为天人...

简化版题面

给定一张无向连通图,定义直径 \(d=\max(dis(i,j)) \quad (i,j \in N)\) ,其中 \(dis(i,j)\) 指的是 \((i,j)\) 之间的最短距离。接下来会有 \(q\) 次加边操作,每次操作之间不独立,操作完成后输出当前无向图的直径。
数据范围 \(n,m,q \leq 10^5\)

正解

首先我们发现,求严格直径是不可做的,那么就必须考虑如何利用误差范围去简化问题。
不妨先考虑上界的运用,假设我们已经求出了直径 \(l\) ,端点为 \(S,T\) ,考虑图上任意一个点 \(p\) ,有这样的结论 \(max(dis(p,S),dis(p,T)) \geqslant \frac{l}{2}\)
考虑证明这个结论,因为 \(dis(p,S)+dis(p,T) \leq l\) ,所以二者的最大值至少占一半。
考虑运用这个结论,不妨让 \(p=1\) ,这样问题就转化成了求1到其他点最短路的最大值,这样就可以求出来一条至少大于 \(\frac{l}{2}\) 的路径。
之后我们把求出来的长度乘二,就得出了当前图的合法答案。

那下界怎么使用呢?
我们发现,这道题中只有加边的操作,所以图中直径的长度肯定是单调不上升的。
这样我们就可以求出当前图的合法直径,然后二分一个状态使得那个状态的合法直径是当前合法直径的一半,就说明这两个状态之间的所有状态中一开始的直径都是合法的。
之后我们再去求下一个状态的合法直径,用二分往后跳。

复杂度 \(O((n+m)\log(q))\)

贴个代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5;
int n,m,q,ans[maxn],maxx=-1;
int qx[maxn],qy[maxn];
struct node{
	int x,time;
};
vector<node>v[maxn];
queue<int>qu;
int dis[maxn];
int bfs(int t){
	memset(dis,0,sizeof(dis));
	//while(!qu.empty()) qu.pop();
	qu.push(1);dis[1]=1;
	while(!qu.empty()){
		int x=qu.front();
		for(int i=0;i<v[x].size();i++){
			int s=v[x][i].x,ti=v[x][i].time;
			if(ti>t) continue;
			if(dis[s]) continue;
			dis[s]=dis[x]+1;qu.push(s);
		}
		qu.pop();
	}
	maxx=-1;
	for(int i=1;i<=n;i++) maxx=max(maxx,dis[i]-1);
	return maxx;
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		v[x].push_back(node{y,1});
		v[y].push_back(node{x,1});
	}
	ans[0]=bfs(1);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&qx[i],&qy[i]);
		v[qx[i]].push_back(node{qy[i],i});
		v[qy[i]].push_back(node{qx[i],i});
	}
	for(int i=1;i<=q;){
		ans[i]=bfs(i);
		int l=i,r=q,mid=(i+r)/2,qans;
		while(r>=l){
			mid=l+r>>1;
			if(bfs(mid)*2>=ans[i]) l=mid+1,qans=mid;
			else r=mid-1;
		}
		for(int j=i;j<=qans;j++) ans[j]=ans[i];
		i=qans+1;
	}
	for(int i=0;i<=q;i++) printf("%d ",ans[i]);
	return 0;
}
`
posted @ 2023-03-18 14:07  sky_light  阅读(48)  评论(0)    收藏  举报