最大权闭合子图

最大权闭合子图

闭合子图:对于有向图 \(G(V,E)\) 它的闭合子图的点集满足所有点的出边指向的点都在点集内,边集为这些出边。

考虑如何将闭合子图转化为流网络。

建一个源点和一个汇点,源点连向权值为正的点,流量为点权。

权值为负的点向汇点连一条边,流量为点权的绝对值,点与点之间的边不变,流量为正无穷。

据此定义闭合子图问题中的简单割:只割源点出去的边或到汇点的边的割。

根据定义我们知道流网络的最小割一定为一个简单割,证明较为显然。

另一个结论:若令割源点边为不选,割汇点边为选,则流网络中简单割与闭合子图一一对应

考虑证明:如果一个点对应的源点出去的边未被割,即被选,那说明这个点的出边对应的汇点边全被割掉,即选上。

如果一个点对应的源点出去的边被割,即未被选,那我们就根本不用考虑它,所以一个简单割一定对应一个闭合子图。

而源点所连向的点,因为它们的出边一定被割了,也即选上了,所以一个闭合子图也一定对应一个简单割。

现在我们成功将闭合子图问题转化为了流网络问题,那怎么计算答案呢?

其实很显然,源点边权值和减去最小割即为答案,因为我们默认源点边是选的,割掉它们自然该减去。

同理,因为默认汇点边是不选的,选上就应该加上它们的权值,又因为权值是负的,就相当于减去割边的流量。

至此,我们将最大权闭合子图问题转化为了一个最小割模型,使用 Dinic 解决即可。

[ARC161F] Everywhere is Sparser than Whole (Judge)

考虑将导出子图换一个描述:若一条边在子图内,则它的两个端点一定在子图内,将边向点连边从而转化为闭合子图问题。

\(\frac{E}{V}\geq D\) 写作 \(E-VD\geq 0\),转化为闭合子图问题,当最大权 \(>0\) 时一定不合法。

当最大权为 \(0\) 时图不一定不合法,因为存在最大流满流即选全图的情况权也为 \(0\)

但这时由于最大流满流,每个点恰好会受到来自 \(D\) 条边的流量,我们可以据此给边定向。

这样定向后,存在密度等于 \(D\) 的真导出子图当且仅当新定向的图不止一个强连通分量。

考虑证明:如果存在多个强连通分量,那没有出边的强连通分量的所有边一定指向内部,密度恰好为 \(D\),得到充分性。

如果存在一个真导出子图密度等于 \(D\),这个点集无论怎么定向都会至少形成一个没有出边的强连通分量,得到必要性。

至此我们将这个题分成了两部分解决,编码时先跑 Dinic 再跑 Tarjan 即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define M 50005*10
#define INF 0x3f3f3f3f
stack<int> st;
vector<int> v[N];
int cnt,dfn,dfl[N],low[N],scc[N];
int T,n,d,s,t,x[N],y[N],nx[N],ny[N];
struct Max_flow{
	queue<int> q;
	vector<int> G[N];
	int cnt=0,ans=0,v[M],w[M],dep[N],cur[N];
	bool bfs(){
		while(!q.empty())  q.pop();
		q.push(s),memset(dep,0,sizeof dep),dep[s]=1;
		while(!q.empty()){
			int x=q.front();q.pop();
			if(x==t) return 1;
			for(auto e:G[x])
				if(!dep[v[e]]&&w[e])
					dep[v[e]]=dep[x]+1,q.push(v[e]);
		}
		return 0;
	}
	int dfs(int x,int flow,int res=0){
		if(x==t)  return flow;
		for(int i=cur[x];i<G[x].size()&&flow;i++){
			cur[x]=i;int e=G[x][i];
			if(dep[v[e]]==dep[x]+1&&w[e]){
				int f=dfs(v[e],min(flow,w[e]));
				w[e]-=f,w[e^1]+=f,flow-=f,res+=f;
			}
		}
		if(res==0)  dep[x]=0;
		return res;
	}
	void clear(int n){for(int i=0;i<=n;i++)  G[i].clear();cnt=ans=0;}
	void add(int x,int y,int z){v[cnt]=y,w[cnt]=z,G[x].push_back(cnt++);}
	int work(int x,int y,int z){add(x,y,z),add(y,x,0);return cnt-2;}
	int max_flow(){while(bfs())  memset(cur,0,sizeof cur),ans+=dfs(s,INT_MAX);return ans;}
}Dinic;
void dfs(int x){
	st.push(x),low[x]=dfl[x]=++dfn;
	for(auto y:v[x]){
		if(!dfl[y])  dfs(y),low[x]=min(low[x],low[y]);
		else if(!scc[y])  low[x]=min(low[x],dfl[y]);
	}
	if(dfl[x]==low[x]){
		cnt++;int y=0;
		while(y!=x)  y=st.top(),st.pop(),scc[y]=cnt;
	}
}
void solve(){
	cin>>n>>d,s=0,t=n*(d+1)+1,cnt=dfn=0;
	for(int i=1;i<=n;i++)  dfl[i]=scc[i]=0,v[i].clear();
	for(int i=1;i<=n*d;i++){
		cin>>x[i]>>y[i],Dinic.work(s,i,1);
		nx[i]=Dinic.work(i,x[i]+n*d,INF);
		ny[i]=Dinic.work(i,y[i]+n*d,INF);
	}
	for(int i=1;i<=n;i++)  Dinic.work(n*d+i,t,d);
	Dinic.max_flow();
	for(int i=1;i<=n*d;i++){
		if(Dinic.w[nx[i]^1])  v[x[i]].push_back(y[i]);
		if(Dinic.w[ny[i]^1])  v[y[i]].push_back(x[i]);
	}
	for(int i=1;i<=n;i++)  if(!dfl[i])  dfs(i);
	Dinic.clear(n*(d+1)+1);
	return cout<<(cnt==1?"Yes":"No")<<"\n",void();
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>T;while(T--)  solve();
}
posted @ 2026-01-03 11:59  tkdqmx  阅读(25)  评论(0)    收藏  举报