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;
}