【UOJ168】元旦老人与丛林【图论证明】【最大权闭合子图】【dinic动态推流】

题意:给一张无向图,判断能否分成两个生成森林。

n ≤ 2 × 1 0 3 , m ≤ 4 × 1 0 3 n\leq 2\times 10^3,m\leq 4\times 10^3 n2×103,m4×103

题目中这样的图称为“丛林”,下面以此来简称。

结论 一张图是丛林的充要条件是它的每一个子图 G = ( ∣ V ∣ , ∣ E ∣ ) G=(|V|,|E|) G=(V,E) ∣ E ∣ ≤ 2 ∣ V ∣ − 2 |E|\leq 2|V|-2 E2V2

必要性显然。

充分性:考虑归纳法,当 ∣ V ∣ = 1 |V|=1 V=1 时显然成立, ∣ V ∣ > 1 |V|>1 V>1 假设结点数小于 ∣ V ∣ |V| V 的满足条件的图都是丛林。

引理1 定义满图为满足 ∣ E ∣ = 2 ∣ V ∣ − 2 |E|=2|V|-2 E=2V2 的图,对于当前图的两个交非空的子图 G 1 = ( V 1 , E 1 ) , G 2 = ( V 2 , E 2 ) G_1=(V_1,E_1),G_2=(V_2,E_2) G1=(V1,E1),G2=(V2,E2),它们的并也是满图。

证明 设它们的交为 G 3 = ( V 3 , E 3 ) G_3=(V_3,E_3) G3=(V3,E3),并为 G = ( V , E ) G=(V,E) G=(V,E)。由假设知 ∣ E 1 ∣ = 2 ∣ V 1 ∣ − 2 , ∣ E 2 ∣ = 2 ∣ V 2 ∣ − 2 , ∣ E 3 ∣ ≤ 2 ∣ V 3 ∣ − 2 |E_1|=2|V_1|-2,|E_2|=2|V_2|-2,|E_3|\leq 2|V_3|-2 E1=2V12,E2=2V22,E32V32,所以 ∣ E ∣ ≥ 2 ∣ V ∣ − 2 |E|\geq 2|V|-2 E2V2。又因为 ∣ E ∣ ≤ 2 ∣ V ∣ − 2 |E|\leq 2|V|-2 E2V2,所以 ∣ E ∣ = 2 ∣ V ∣ − 2 |E|=2|V|-2 E=2V2。得证。

引理2 一个丛林最小的点度数小于 4 4 4

证明 显然。

对于一张满足右边的条件的图,它的每一个真子图都是丛林。我们考虑它的一个最小的点的度数,如果是 0 , 1 , 2 0,1,2 0,1,2,就把这些边连到外面对应个数的生成森林上,得到整张图是丛林。下面讨论度数为 3 3 3 的情况。

引理3 当度数为 3 3 3 时,设相邻的三个点为 a , b , c a,b,c a,b,c,删掉这个点 u u u 及这三条边后的图为 G G G,那么一定存在 { x , y } ⊆ { a , b , c } \{x,y\}\subseteq \{a,b,c\} {x,y}{a,b,c} 使得 G G G 中不存在一个满子图包含 x , y x,y x,y

证明 假设 a , b , c a,b,c a,b,c 两两都被一个满子图包含,把这三个满子图合并起来,由引理 1,合并后的图 F F F 也是满子图,即 ∣ E ( F ) ∣ = 2 ∣ V ( F ) ∣ − 2 |E(F)|=2|V(F)|-2 E(F)=2V(F)2。我们加入 u u u 和这三条边,就得到了一张 ∣ E ∣ = 2 ∣ V ∣ − 1 |E|=2|V|-1 E=2V1 的图,它是原图的子图,矛盾,得证。

我们在 G 1 G_1 G1 中加入一条边 ( x , y ) (x,y) (x,y),因为不存在包含 ( x , y ) (x,y) (x,y) 的满图,所以加入后 G 1 G_1 G1 仍然是丛林。考虑这棵丛林的两棵生成树,在包含 ( x , y ) (x,y) (x,y) 的树上断掉这条边,然后 u u u 分别通过 x , y x,y x,y 连接两个连通分量,剩下一个直接连,就构造了两棵原图的生产树,原命题得证。


现在我们要判断是否对于每一个子图都有

∣ E ∣ ≤ 2 ∣ V ∣ − 2 |E|\leq 2|V|-2 E2V2

∣ E ∣ − 2 ∣ V ∣ ≤ − 2 |E|-2|V|\leq -2 E2V2

就是最大权闭合子图对于每条边建一个点,从 S S S 连边权为 1 1 1 的边,向两个端点连 + ∞ +\infin + 的边,每个点到 T T T 连边权为 2 2 2 的边,跑最小割即可。

设最小割为 c c c,边数为 m m m,我们需要判断是否

m − c ≤ − 2 m-c\leq -2 mc2

然后你会发现至少有一个大小为 m m m 的割,所以会永远输出 No

冷静分析,这样的原因是空图的存在。所以我们要枚举一个点强制选,也就是断掉对应的边,最后最小割 + 2 +2 +2,当 c − 2 < m c-2<m c2<m 时输出 No

然后要跑 n n n 次完整的 dinic,会 T。注意到每次流量改变是常数,所以在断掉 ( u , v ) (u,v) (u,v) 时从 u u u S S S c ( u , v ) c(u,v) c(u,v) 的流量来模拟退流,然后物理断掉这条边。再从 S S S T T T 跑 dinic,因为是在几乎已经增广完的残余网络上跑的所以很快。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <queue>
#define MAXN 6005
#define MAXM 40005
using namespace std;
const int INF=0x7fffffff;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
struct edge{int u,v,c;}e[MAXM];
int head[MAXN],cur[MAXN],nxt[MAXM],cnt=1;
inline void insert(int u,int v,int c){e[++cnt]=(edge){u,v,c};nxt[cnt]=head[u];head[u]=cnt;}
inline void addnode(int u,int v,int c){insert(u,v,c),insert(v,u,0);}
int dis[MAXN];
bool bfs(int S,int T)
{
	queue<int> q;
	q.push(T);
	memset(dis,-1,sizeof(dis));
	dis[T]=0;
	while (!q.empty())
	{
		int u=q.front();q.pop();
		for (int i=head[u];i;i=nxt[i])
			if (e[i^1].c&&dis[e[i].v]==-1)
			{
				dis[e[i].v]=dis[u]+1;
				q.push(e[i].v);
				if (e[i].v==S) return true;
			}
	}
	return false;
}
int dfs(int u,int f,int T)
{
	if (u==T||!f) return f;
	int used=0;
	for (int& i=cur[u];i;i=nxt[i])
		if (e[i].c&&dis[u]==dis[e[i].v]+1)
		{
			int w=dfs(e[i].v,min(e[i].c,f),T);
			e[i].c-=w,e[i^1].c+=w;
			f-=w,used+=w;
			if (!f) break;
		}
	if (!used) dis[u]=-1;
	return used;
}
inline int dinic(int S,int T,int f=INF)
{
	int ans=0;
	while (f>ans&&bfs(S,T))
		memcpy(cur,head,sizeof(head)),ans+=dfs(S,f-ans,T);
	return ans;
}
int pos[MAXN];
int solve()
{
	memset(head,0,sizeof(head));
	memset(nxt,0,sizeof(nxt));
	cnt=1;
	int n,m;
	n=read(),m=read();
	int S=n+m+1,T=S+1;
	for (int i=1;i<=m;i++)
	{
		addnode(n+i,read(),INF);
		addnode(n+i,read(),INF);
		addnode(S,n+i,1);
	}
	for (int i=1;i<=n;i++) addnode(i,T,2),pos[i]=cnt;
	int ans=dinic(S,T);
	for (int i=1;i<=n;i++)
	{
		int f=e[pos[i]].c;
		e[pos[i]].c=e[pos[i]^1].c=0;
		if (f) ans-=dinic(i,S,f);
		if (i>1) e[pos[i-1]^1].c=2;
		ans+=dinic(S,T);
		if (ans<m) return puts("No"),0;
	}
	puts("Yes");
	return 0;
}
int main()
{
	for (int T=read();T;T--) solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值