快速求圖上最小點定聯通塊權值的Trick

HarlemBlog發表於2024-10-30
更新日誌

概念

圖上最小點定連通塊,就是給出無向連通圖上一些點,要求找出邊權和最小的連通分量使這些點強連通。

現在要求這個連通塊內的邊權之和。

思路

先給出結論:把節點按照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;
}

相關文章