快速求图上最小点定联通块权值的Trick

更新日志

概念

图上最小点定连通块,就是给出无向连通图上一些点,要求找出边权和最小的连通分量使这些点强连通。

现在要求这个连通块内的边权之和。

思路

先给出结论:把节点按照dfs序排序,统计所有相邻的节点以及起始点与末尾点之间的距离,将它们求和,所求的答案即为这个和除以2。

感性证明一下,可以想象一个人(或者蚂蚁,或者飞船,或者任何能沿着路走的生物,或者不是生物也行),在dfs生成树上走上、走下,依次走向每一个节点,最后走会原点,不难想象到每一条边都被他走了两遍。

稍微理性一点的话,因为是按照dfs序依次排列的,就有几种情况:

  • 一点是另一点的祖先节点,直接走下去。
  • 二者属于不同子树,先走回到公共祖先节点(必然是之前已经经过的节点),再走进一条新的路径
    重复二个操作,那么都是先走到最深的节点,再回溯到某个点,再沿着另一个路径搜索……最后回到起始点,每条边就都被走了两次。

例题

CF372D

代码

前注:非题解,不做详细讲解

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5,T=32;

int n,k;
int ans;
int distal;

vector<int> vs[N];
set<int> dst;

int dp[N];
int f[N][T+5];
void brinit(){
	for(int t=1;t<=T;t++){
		for(int i=1;i<=n;i++){
			f[i][t]=f[f[i][t-1]][t-1];
		}
	}
}
int lca(int a,int b){
	if(dp[a]>dp[b])swap(a,b);
	for(int t=T;t>=0;t--){
		if(dp[f[b][t]]>=dp[a])b=f[b][t];
	}
	if(a==b)return a;
	for(int t=T;t>=0;t--){
		if(f[a][t]!=f[b][t]){
			a=f[a][t];
			b=f[b][t];
		}
	}
	return f[a][0];
}
int dis(int a,int b){
	int c=lca(a,b);
	return dp[a]-dp[c]+dp[b]-dp[c];
}

int cnt;
int dfn[N];
void dfs(int now,int fa){
	dfn[now]=++cnt;
	f[dfn[now]][0]=dfn[fa];
	dp[dfn[now]]=dp[dfn[fa]]+1;
	for(auto nxt:vs[now]){
		if(nxt==fa)continue;
		dfs(nxt,now);
	}
}

void pushin(int x){
	dst.insert(dfn[x]);
	int now,lst,nxt;
	now=lst=nxt=0;
	auto plc=dst.lower_bound(dfn[x]);
	now=*plc;
	auto lstp=plc;
	if(lstp==dst.begin())lst=*dst.rbegin();
	else{advance(lstp,-1);lst=*lstp;}
	auto nxtp=plc;advance(nxtp,1);
	if(nxtp==dst.end())nxt=*dst.begin();
	else nxt=*nxtp;
	distal-=dis(lst,nxt);
	distal+=dis(lst,now);
	distal+=dis(now,nxt);
}

void throut(int x){
	int now,lst,nxt;
	now=lst=nxt=0;
	auto plc=dst.lower_bound(dfn[x]);
	now=*plc;
	auto lstp=plc;
	if(lstp==dst.begin())lst=*dst.rbegin();
	else{advance(lstp,-1);lst=*lstp;}
	auto nxtp=plc;advance(nxtp,1);
	if(nxtp==dst.end())nxt=*dst.begin();
	else nxt=*nxtp;
	distal+=dis(lst,nxt);
	distal-=dis(lst,now);
	distal-=dis(now,nxt);
	dst.erase(dfn[x]);
}

inline bool check(){
	return distal/2+1<=k;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>k;
	int a,b;
	for(int i=1;i<n;i++){
		cin>>a>>b;
		vs[a].push_back(b);
		vs[b].push_back(a);
	}
	dfs(1,1);
	brinit();
	for(int i=0,j=1;i<=n;pushin(++i)){
		while(!check()){
			throut(j++);
		}
		ans=max(ans,i-j+1);
	}
	cout<<ans;
	return 0;
}
posted @ 2024-10-30 22:05  HarlemBlog  阅读(5)  评论(0编辑  收藏  举报