首页 > 其他分享 >Luogu3731 新型城市化 - 二分图 - 网络流 - 强连通分量 -

Luogu3731 新型城市化 - 二分图 - 网络流 - 强连通分量 -

时间:2023-03-03 18:34:10浏览次数:56  
标签:二分 连通 int LL rem flow maxn Luogu3731 now

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

题解:
考虑原图的补图,因为题目中保证了城市群最多有两个,因此补图是一个二分图,城市群等价于独立集
原题转化成了,删去一条边之后最大独立集增大
而最大独立集 = 最大匹配
也就是说我们要求出最大匹配必经的边
最大匹配好求,把二分图的边的容量设为 1,跑最大流即可
考虑如何求必经的边,先给结论:一条边必经,当且仅当这条边满流,且边的两个点不在残量网络的同一个强连通分量中
第一个条件很显然,考虑第二个条件。
首先由于容量为 1,残量网络中的边必然都是没有匹配上的
如果两个点在同一个强连通分量中,意味着可以经过别的结点从而进行匹配,这与必经相矛盾
一个细节:网络流建图是有反向边的,而反向边的容量为 0,因此如果反向边出现在残量网络中意味着正向边成功匹配了,这也对应着网络流的“反悔”操作,即不选正向边(流反向边)进行匹配

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5;

int n,m;
struct ed{
	LL from,to,cap,flow,rev;
	ed(){}
	ed(LL from,LL to,LL cap,LL flow,LL rev):from(from),to(to),cap(cap),flow(flow),rev(rev){}
};
vector<ed>g[maxn];
vector<int>og[maxn];
int col[maxn];

struct netflow{
	int cur[maxn]; 
	int d[maxn], q[maxn], hd, tl;
	int s, t;	// 源 汇 
	
	netflow(){s=t=-1;}
	
	void init(int s0,int t0){
		s = s0, t = t0;
	}

	void add(int x,int y,LL v){
		g[x].push_back(ed(x,y,v,0,g[y].size()));
		g[y].push_back(ed(y,x,0,0,g[x].size() - 1));
	}
	
	int bfs(){
		memset(d,0, sizeof d);
		hd = tl = 0;
		q[tl ++] = s;
		d[s] = 1;
		while(hd != tl){
			int now = q[hd ++];
			for(int i = 0;i<g[now].size();i++){
				ed &e = g[now][i];
				if(!d[e.to] && e.cap > e.flow)d[e.to] = d[now] + 1, q[tl ++] = e.to;
			}
		}
		return d[t];
	}
	
	LL dfs(int now,LL rem){	// rem 当前流量 
		if(now == t || !rem)return rem;
		LL flow = 0;
		for(int &i = cur[now]; i < g[now].size();i ++){
			ed &e = g[now][i];
				// 分层图 & 残量不为0 
			if(d[e.to] == d[now] + 1 && e.cap > e.flow){
				LL f = dfs(e.to, min(rem, e.cap - e.flow));
				rem -= f, flow += f, e.flow += f, g[e.to][e.rev].flow -= f;
			}
			if(!rem)break;
		}
		if(rem)d[now] = -1;
		return flow;
	}
	
	LL dinic(){
		assert(s!=-1);
		LL flow = 0;
		while(bfs()){
			memset(cur, 0, sizeof cur);
			flow += dfs(s, 1ll << 62);
		}
		return flow;
	}
}nf;

pii edge[maxn];
vector<int>ng[maxn], h[maxn];
struct _scc{
	// 存图 g[] 缩点之后在 h[]
	// ed[] 保存原图的 m 个有向边 
	int dfn[maxn], low[maxn], dfs_clock;
	int stk[maxn], tp = 0, belong[maxn], tot=0, sz[maxn];
	// belong[i] i 属于哪个强连通块 tot 块数(新的n)sz 每个块的大小 
	_scc(){memset(dfn,0,sizeof dfn);}
	
	void tarjan(int x){
		dfn[x] = low[x] = ++ dfs_clock; 
		stk[++ tp] = x;
		for(int i=0;i<g[x].size();i++){
			int u = g[x][i].to;
			if(g[x][i].flow == g[x][i].cap)continue;
//			printf("[%d,%d] %d\n",x,u,g[x][i].flow);
			if(!dfn[u]){
				tarjan(u);
				low[x] = min(low[x], low[u]);
			}else if(!belong[u])
				low[x] = min(low[x], dfn[u]); 
		}
		if(low[x] == dfn[x]){
			belong[x] = ++tot;
			++sz[tot];
			while(stk[tp] != x){
				++sz[tot];
				belong[stk[tp]] = tot;
				-- tp;
			}
			-- tp;
		}
	}
	
	void getscc(){
		for(int i=1;i<=n;i++)
			if(!dfn[i])tarjan(i);
	}
};
struct _scc scc;

void dfs(int x,int c){
	col[x] = c;
	for(int u : og[x])if(!col[u]){
		dfs(u, 3-c);
	}
}

signed main(){
//	freopen("Luogu3731.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;scanf("%d%d",&x,&y);
		og[x].pb(y), og[y].pb(x);
	}
	for(int i=1;i<=n;i++)if(!col[i])dfs(i,1);
	int s = n+2, t = n+1;
	nf.init(s, t);
	
	int ec = 0;
	for(int i=1;i<=n;i++)if(col[i] == 1){
		for(auto it : og[i]){
			int x = i, y = it;
//			printf("%d %d %d\n",x,y,col[x]+col[y]);
			nf.add(x,y,1);
			ng[x].pb(y);
//			printf("(%d,%d)\n",x,y);
			edge[++ec] = mpr(x, y);
		}
	}
	
	for(int i=1;i<=n;i++)
		if(col[i] == 1)nf.add(s, i, 1);
		else nf.add(i, t, 1);

	ll res = nf.dinic();
	scc.getscc();

	vector<pii>ans;
	for(int i=1;i<=n;i++)if(col[i] == 1)
		for(int j=0;j<g[i].size();j++){
			ed u = g[i][j];
//			printf("! %d\n",u.flow);
			if(u.flow == 1){
				if(scc.belong[i] != scc.belong[u.to]){
					ans.emplace_back(min(i,(int)u.to), max(i,(int)u.to)); 
//				printf("%d %d\n",i,u.to);
			}
		}
	}
	
	sort(ans.begin(), ans.end(), [&](pii a,pii b){
		return a.first == b.first ? a.second < b.second : a.first < b.first;
	});
	printf("%d\n",ans.size());
	for(auto u : ans)
		printf("%d %d\n",u.first,u.second);

	return 0;
}

标签:二分,连通,int,LL,rem,flow,maxn,Luogu3731,now
From: https://www.cnblogs.com/SkyRainWind/p/17176631.html

相关文章

  • hdu-2063 二分图
    http://acm.hdu.edu.cn/showproblem.php?pid=2063过山车TimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmi......
  • 牛客小白月赛67—— 一刀二分三角(数学)
    https://ac.nowcoder.com/acm/contest/51458/C题目大意:给定一个三角形,三个点分别是(0,0)(xc,yc)(xb,0)。​问我们是否可以将三角形沿着x=某个数字切开,得到的两个平面图形面......
  • Leetcode——二分法bisect_left,bisect_right
    !前提——列表有序case1如果列表中没有元素x,那么bisect_left(ls,x)和bisec_right(ls,x)返回相同的值,该值是x在ls中“合适的插入点索引,使得数组有序”。此时,ls[index2]......
  • 代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素。
    LeetCode704-二分查找题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返......
  • 基本功练习_2_27_之二分法查找
    #include<stdio.h>intsearch(int*a,intkey,intlow,inthigh){intmid;if(low>high)return-1;mid=(low+high)/2;if(a[mid]==key)returnmid;elseif(a[m......
  • 算法基础1.2.2浮点数二分
    前言直接先把整数二分看完看整数二分文章点这里现在只需要补充几个特点(其实整数二分的博客的前言就介绍了一些)就可以了由于浮点数二分没有向下取整的特性(不懂的话就去......
  • 基本算法之二分查找法折半查找(Java)
    前提条件:数组中的数据必须是有序的!核心思想:每次排除一半的数据,查询数据的性能明显提高很多!      publicclassTask{publicstaticvoidmain(Stri......
  • 算法基础1.2.1整数二分
    前言如果第一次接触二分其实很难理解它的含义我对二分的理解就是找到一个条件,能够保证所有数据对于这个条件要么是True要么是False。二分的作用是查找。二分本质不是单......
  • leetcode之——二分法模板
    classSolution:defsearch(self,nums:List[int],target:int)->int:n=len(nums)left,right=0,n-1whileleft<=right:k=(right-left)//2+left......
  • 寻找旋转排序数组中的最小值---二分查找
    寻找二分排序数组中的最小值已知一个长度为n的数组,预先按照升序排列,经由1到n次旋转后,得到输入数组。例如,原数组nums=[0,1,2,4,5,6,7]在变化后可能得到:若旋转......