1. 程式人生 > 其它 >CF GYM 103049J

CF GYM 103049J

題意概述

\(n\) 個點 \(m\) 條邊的無向圖,選出一條路徑後去掉路徑上的點,將剩下的點分成點數相等的兩份並且使得兩份之間沒有邊連線。

資料範圍

\(n,m\leq 2 \times 10^5\)

題解

首先對無向圖建出 \(\text{DFS}\) 樹,這樣就只有返祖邊而沒有橫叉邊了

然後我們從根往下走,選擇出一條鏈將其刪掉,由於只有返祖邊所以這樣一定將原圖斷成和該鏈相連的若干個連通塊

在每個點處將每個兒子按子樹大小排序,按順序掃一遍除重兒子外的所有兒子:如果 \(a>b\) 那麼將它加入 \(b\),如果 \(b>a\) 那麼將它加入 \(a\),不難發現這樣一定可以使 \(|a-b|\leq \text{size}_{maxson}\)

。那麼我們如果在當前結束不了刪除就遞迴進重兒子繼續構造即可。

略微證明一下:考慮初始 \(|a-b|\) 小於重兒子,那麼顯然用上面的策略 \(|a-b|\) 不會大於重兒子,如果 \(|a-b|\) 初始值大於重兒子,由於它在上一層小於上一層的重兒子,那麼這一層除重兒子之外的點全部用來填補這個差一定是可以填補得到 \(b>a\) 的,由於在第一層 \(a=b\) ,所以歸納證明正確。
這樣遞迴到最後一層,就一定有 \(|a-b|\leq 1\) ,一定能構造出來方案。

PS:無向圖建出 \(\text{DFS}\) 樹,沒有返祖邊這個性質好像十分有用。

const int N=2e5+5;
int n,m,x,y,siz[N],son[N],vis[N];
vi g[N],f[N],v[N],a,b,d;
void work(int u){
	vis[u]=1;
	for(auto i:g[u]){
		if(vis[i])continue;
		work(i);
		v[u].pb(i);
	}
}
void dfs(int u){
	siz[u]=1;
	for(auto i:v[u]){
		dfs(i);
		siz[u]+=siz[i];
		if(siz[son[u]]<siz[i])son[u]=i;
	}
}
int cmp(int x,int y){
	return siz[x]<siz[y];
}
void help(int u,int id){
	if(!u)return ;
	if(id==1)a.pb(u);
	else b.pb(u);
	for(auto i:v[u]){
		help(i,id);
	}
}
void dp(int u,int a,int b){
	sort(v[u].begin(),v[u].end(),cmp);
	d.pb(u);
	for(auto i:v[u]){
		if(i==son[u])continue;
		if(a<=b)help(i,1),a+=siz[i];
		else help(i,2),b+=siz[i];
	}
	if(a<=b && a+siz[son[u]]==b){
		help(son[u],1);
		return ;
	}
	if(a>=b && b+siz[son[u]]==a){
		help(son[u],2);
		return ;
	}
	dp(son[u],a,b);
}
int main(){
	#ifdef newbiewzs
	#else
	#endif
	n=read();m=read();
	for(int i=1;i<=m;i++){
		x=read();y=read();
		g[x].pb(y);
		g[y].pb(x);
	}
	work(1);
	dfs(1);
	dp(1,0,0);
	cout<<d.size()<<" "<<a.size()<<endl;
	for(auto i:d){
		cout<<i<<" ";
	}
	cout<<'\n';
	for(auto i:a){
		cout<<i<<" ";
	}
	cout<<'\n';
	for(auto i:b){
		cout<<i<<" ";
	}
	return 0;
}