D. Nastia Plays with a Tree
题意:n个节点的树,用最少的操作次数使得每个节点的度数<=2保证最后也是一棵树。
操作:
- 选择一条已存在的边删掉
- 在任意两个节点间添加一条边
输出:最小的操作次数以及每次的删除边的节点以及加边的节点
思路:看题解没写网络流解法,那我来写一下。
- 首先看复杂度如果线性建边:O(n*sqrt(n))
- 树是一个特殊的二分图,可以染色后保持原来的边不变且flow=1
- S——左部点 flow=2. 右部点——>T flow=2.
- 这样跑出最大流之后在残量网络上扫一遍,那些被删除的边就肯定是流量为0的边
- 至于加边的过程:对于残量网络必定是个森林(以flow的视角来看)那么只需要在森林里面的叶子节点之间加边让森林之间联通就行
Code:
int T;
std::vector<int> G[N],B[N];
int u[N],v[N],col[N],fa[N],deg[N],a[N];
void dfs(int u,int fa,int c){
col[u]=c;
for(int v:G[u]){
if(v==fa)continue;
dfs(v,u,c^1);
}
}
int Find(int x){
return x==fa[x]?x:fa[x]=Find(fa[x]);
}
int join(int x,int y){
x=Find(x),y=Find(y);
fa[x]=y;
}
signed main() {
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
G[i].clear();
B[i].clear();
fa[i]=i;
col[i]=0;
deg[i]=0;
}
for(int i=1;i<n;i++){
scanf("%d%d",&u[i],&v[i]);
G[u[i]].push_back(v[i]);
G[v[i]].push_back(u[i]);
}
dfs(1,-1,0);
s=n+1,t=n+2;
ac.init(n);
for(int i=1;i<=n;i++){
if(col[i]==0)ac.addedge(s,i,2);
else ac.addedge(i,t,2);
}
for(int i=1;i<n;i++){
if(col[u[i]]==0){
ac.addedge(u[i],v[i],1);
}
else{
ac.addedge(v[i],u[i],1);
}
}
ac.Dinic();
map<pair<int,int>,int>mp;
for(int u=1;u<=n;u++){
for(int i=head[u]; i; i=edge[i].next) {
int v=edge[i].to;
if(v>n)continue;
else if(edge[i].flow==1)mp[{v,u}]=1,mp[{u,v}]=1,join(u,v),deg[u]++,deg[v]++;
}
}
std::vector<pair<int,int>>ans1,ans2;
for(int i=1;i<n;i++){
if(mp.count({u[i],v[i]}))continue;
else ans1.push_back({u[i],v[i]});
}
cnt=0;
for(int i=1;i<=n;i++){
if(deg[i]==1){
int x=Find(i);
B[x].push_back(i);
a[++cnt]=x;
}
else if(deg[i]==0){
B[i].push_back(i),B[i].push_back(i);
a[++cnt]=i;
}
}
sort(a+1,a+cnt+1);
cnt=unique(a+1,a+cnt+1)-a-1;
for(int i=1;i<=ans1.size();i++)ans2.push_back({B[a[i]][0],B[a[i+1]][1]});
printf("%d\n",ans1.size());
for(int i=0;i<ans1.size();i++){
printf("%d %d %d %d\n",ans1[i].first,ans1[i].second,ans2[i].first,ans2[i].second);
}
}
}