网络流 一定在最大匹配里的边
P3731 [HAOI2017]新型城市化
P3731 [HAOI2017]新型城市化(https://www.luogu.com.cn/problem/P3731)
题目描述
Anihc国有n座城市.城市之间存在若一些贸易合作关系.如果城市x与城市y之间存在贸易协定.那么城市文和城市y则是一对贸易伙伴(注意:(x,y)和(y,x))是同一对城市)。
为了实现新型城市化.实现统筹城乡一体化以及发挥城市群辐射与带动作用.国 决定规划新型城市关系。一些城市能够被称为城市群的条件是:这些城市两两都是贸易伙伴。 由于Anihc国之前也一直很重视城市关系建设.所以可以保证在目前已存在的贸易合作关系的情况下Anihc的n座城市可以恰好被划分为不超过两个城市群。
为了建设新型城市关系Anihc国想要选出两个之前并不是贸易伙伴的城市.使这两个城市成为贸易伙伴.并且要求在这两个城市成为贸易伙伴之后.最大城市群的大小至少比他们成为贸易伙伴之前的最大城市群的大小增加1。
Anihc国需要在下一次会议上讨论扩大建设新型城市关系的问题.所以要请你求出在哪些城市之间建立贸易伙伴关系可以使得这个条件成立.即建立此关系前后的最大城市群的 大小至少相差1。
输入格式
第一行2个整数n,m.表示城市的个数,目前还没有建立贸易伙伴关系的城市的对数。
接下来m行,每行2个整数x,y表示城市x,y之间目前还没有建立贸易伙伴关系。
输出格式
第一行yi个整数ans,表示符合条件的城市的对数.注意(x,y)与(y,x)算同一对城市。
接下来Ans行,每行两个整数,表示一对可以选择来建立贸易伙伴关系的城市。对于 一对城市x,y请先输出编号更小的那一个。最后城市对与城市对之间顺序请按照字典序从小到大输出。
输入输出样例
输入
5 3
1 5
2 4
2 5
输出
2
1 5
2 4
说明/提示
数据规模与约定
数据点1: n≤16
数据点2: n≤16
数据点3~5: n≤100
数据点6: n≤500
数据点7~10: n≤10000
对于所有的数据保证:n <= 10000,0 <= m <= min (150000,n(n-1)/2).保证输入的城市关系中不会出现(x,x)这样的关系.同一对城市也不会出现两次(无重边.无自环)。
思路
我们把每个城市看作一个点,在“当前没有贸易关系”的城市之间连边。
此时,如果一个城市集合是一个城市群,那么这个城市集合中的任意两个城市之间都没有边。
因为“可以划分为两个城市群”,所以这个图是个二分图。
那么“最大城市群”就是二分图的最大独立集。
“在两个城市之间建立贸易关系”即删除这两个点之间的边。
所以题目实际上是,给一个二分图,问删掉哪些边之后,最大独立集的大小会增加。
考虑如何求最大独立集大小。
最大独立集大小=总点数-最小覆盖集大小=最大匹配数。
也就是说,这个题问的是,给一个二分图,问删掉哪些边之后,最大匹配的数量会减少,也就是问,哪些边一定在最大匹配里。
考虑如下定理:若一条边一定在最大匹配中,则在最终的残量网络中,这条边一定满流,且这条边的两个顶点一定不在同一个强连通分量中。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10000 + 10;
const int INF = 0x3f3f3f3f;
//注释为弧优化
struct node {
int form, to, cap, flow, next;
} edge[2000006];
int head[maxn];
int cnt;
struct max_Folw {
int d[maxn], cur[maxn], start, tend;
bool vis[maxn];
void init(int s, int t) {
memset(head, -1, sizeof(head));
cnt=0;
start=s, tend=t;
}
void add(int start, int to, int cap) {
edge[cnt].form = start;
edge[cnt].to = to;
edge[cnt].cap = cap;
edge[cnt].flow = 0;
edge[cnt].next = head[start];
head[start] = cnt++;
}
void AddEdge(int start, int to, int cap) {
add(start, to, cap);
add(to, start, 0);
}
bool BFS() {
memset(d, -1, sizeof(d));
int Q[maxn * 2];
int Thead, Ttail;
Thead = Ttail = 0;
Q[Ttail++] = tend;
d[tend] = 0;
while (Thead<Ttail) {
int x = Q[Thead];
if (x == start)
return true;
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //没有标记,且可行流大于0
d[temp] = d[x] + 1;
Q[Ttail++] = temp;
}
}
Thead++;
}
return false;//汇点是否成功标号,也就是说是否找到增广路
}
int DFS(int x, int cap) {
if (x == tend)
return cap;
int flow = 0, f;
//for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
edge[i].flow += f;
edge[i ^ 1].flow -= f;
flow += f;
if (flow == cap)
return flow;
}
}
d[x] = -2;//防止重搜
return flow;
}
int maxflow() {
int flow = 0, f;
while (BFS()) {
//memcpy(cur, head, sizeof head);
flow += DFS(start, INF);
}
return flow;
}
} flow;
int scc[maxn];
struct Tarjn{
int low[maxn];
int dfn[maxn];
int vis[maxn];
int T, N=0;
stack<int> s;
void tarjn(int u){
low[u]=dfn[u]=++T;
s.push(u), vis[u]=1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int to=edge[i].to;
if(edge[i].cap-edge[i].flow==0) continue;//满流不能再访问
if(!dfn[to]){//没有访问过
tarjn(to);
low[u]=min(low[u], low[to]);
}
else if(vis[to]){//在栈中
low[u]=min(low[u], dfn[to]);
}
}
if(low[u]==dfn[u]){
++N;
while(1){
int now=s.top(); s.pop();
vis[now]=0;
scc[now]=N;
if(now==u){
break;
}
}
}
}
}ta;
vector<int> G[10005], pos[2];
int vis[10005];
void DFS(int u, int y){
vis[u]=y; pos[y].push_back(u);
for(auto x: G[u]){
if(vis[x]==-1){
DFS(x, !y);
}
}
}
struct Node{
int x, y;
};
vector<Node> ans;
int main() {
int n, m;
scanf("%d%d", &n, &m);
int s=0, t=n+1;
flow.init(s, t);
for(int i=1; i<=m; i++) {
int x, y, c;
scanf("%d%d", &x, &y);
G[x].push_back(y);
G[y].push_back(x);
}
memset(vis, -1, sizeof(vis));
for(int i=1; i<=n; i++){//二分图染色
if(vis[i]==-1){
DFS(i, 0);
}
}
for(auto x: pos[0]){
flow.AddEdge(s, x, 1);
for(auto y: G[x]){
flow.AddEdge(x, y, 1);
}
}
for(auto x: pos[1]){
flow.AddEdge(x, t, 1);
}
flow.maxflow();
for(int i=1; i<=n; i++){
if(!ta.dfn[i]){//强连通缩点
ta.tarjn(i);
}
}
for(int i=0; i<cnt; i+=2){
int x=edge[i].form, y=edge[i].to;
if(x==s||x==t||y==s||y==t) continue;
if(edge[i].cap-edge[i].flow==0&&scc[x]!=scc[y]){//一定是在最大流中
if(x>y) swap(x, y);
ans.push_back({x, y});
}
}
sort(ans.begin(), ans.end(), [](Node &a, Node &b){
if(a.x==b.x){
return a.y<b.y;
}
return a.x<b.x;
});
printf("%d\n", ans.size());
for(auto x: ans){
printf("%d %d\n", x.x, x.y);
}
return 0;
}
/*
5 5 1 5
1 2 5
2 3 3
3 5 3
2 4 2
4 5 2
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)