AT4505-[AGC029F]Construction of a tree【构造题,hall定理,网络流】

1|0正题

题目链接:https://www.luogu.com.cn/problem/AT4505


1|1题目大意

给出n个点和n1个点集Ui,每个点集中选择两个点连边使得该图是一棵树。求方案。

n[1,105],i=1n1|Ui|[1,2105]


1|2解题思路

冬令营上讲的题目,现在来写。(而且好像我记得课上讲的做法是bitset的,还是时间久了我记岔了?)

第一眼看上去直觉像是hall定理但还是不会。

hall定理:2n个点的二分图匹配,如果满足任意k个点都连接了不少于k个点的话,那么这张图就有完全匹配。

先套一下试试,发现满足条件的图对于它的每个子图S满足该子图是一个森林。

换句话说对于任意一个U的集合TG(T)表示选出的边连接的节点个数,那么一定有G(T)|T|+1

回顾一下hall定理发现是不是很像。

可以先给每个点集选出一个各不同的点(也就是跑一次匹配),如果选不出来那么显然无解。

然后考虑另一个点的选择,从没有被选择的那个点入手,这个点可以选择任何一个包含它的点集连接出去,然后就从下一个点集开始,直到回溯回来选择下一个。如果最后能够遍历所有点就是合法的。

考虑一下正确性,如果它不能遍历所有点那么没有被遍历的点集T无论怎么连接外面,就一定有一个环不满足G(T)|T|+1。如果它能遍历所有点,那么我们已经构造出一个方案,显然合法。

时间复杂度O(i=1n1|E|n+n)


1|3code

#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int N=4e5+10,inf=1e9; struct node{ int to,next,w; }a[N*2]; int n,s,t,tot=1,cnt,ans; int ls[N],p[N],b[N],dep[N],cur[N]; queue<int> q;bool v[N]; void addl(int x,int y,int w){ a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w; a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=0; return; } bool bfs(){ for(int i=1;i<=t;i++) cur[i]=ls[i],dep[i]=0; while(!q.empty())q.pop();q.push(s); dep[s]=1; while(!q.empty()){ int x=q.front();q.pop(); for(int i=ls[x];i;i=a[i].next){ int y=a[i].to; if(dep[y]||!a[i].w)continue; dep[y]=dep[x]+1; if(y==t)return 1; q.push(y); } } return 0; } int dinic(int x,int flow){ if(x==t)return flow; int rest=0,k; for(int &i=cur[x];i;i=a[i].next){ int y=a[i].to; if(dep[x]+1!=dep[y]||!a[i].w)continue; rest+=(k=dinic(y,min(a[i].w,flow-rest))); a[i].w-=k;a[i^1].w+=k; if(rest==flow)return rest; } if(!rest)dep[x]=0; return rest; } void dfs(int x){ cnt++; for(int i=ls[x];i;i=a[i].next){ int y=a[i].to; if(v[y])continue; b[y]=x;v[y]=1;dfs(p[y]); } } int main() { scanf("%d",&n);s=2*n;t=s+1; for(int i=1;i<n;i++){ int m;scanf("%d",&m); for(int j=1;j<=m;j++){ int x;scanf("%d",&x); addl(x,i+n,1); } } for(int i=1;i<=n;i++)addl(s,i,1); for(int i=1;i<n;i++)addl(i+n,t,1); while(bfs()) ans+=dinic(s,inf); if(ans<n-1)return puts("-1")&0; for(int x=n+1;x<s;x++) for(int i=ls[x];i;i=a[i].next) if(a[i].w){p[x]=a[i].to;break;} v[s]=1; for(int i=ls[s];i;i=a[i].next) if(a[i].w)dfs(a[i].to); if(cnt<n)return puts("-1")&0; for(int x=n+1;x<s;x++) printf("%d %d\n",p[x],b[x]); return 0; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14391688.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(51)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示