UOJ168. 【UR #11】元旦老人与丛林

给出个无向图,问是否存在一种方式把边集划分成两个不为空的边集,使得两个边集分别都是森林。

\(n\le 2000,m\le 2n\)


有结论:当且仅当每个子图都满足\(|E|\le 2|V|-2\)的时候,存在方案。

以下证明其充分性:

考虑归纳,即已经证明\(|V|\)更小的满足条件。以下称\(|E|=2|V|-2\)的图是满的

设当前图为\(G\)

对于1度点,可以直接删掉,连着的边任意分;对于2度点,也可以删掉,连着的边分在不同边集。又因\(|E|\le 2|V|-2\),所以一定存在3度点。任意选一个3度点,记为\(u\),它连出去的三个点为\(a,b,c\)

\(G_1\)表示\(G\)中删去\(u\)形成的子图。

引理1:如果图中存在两个在点相交的满子图,那么它们的并也是满的。

设两个子图以及它们的交、并分别为\(G_1=(V_1,E_1),G_2,G_3,G\)

要证\(|E|=2|V|-2\)。由\(|E|\le 2|V|-2\)\(|E_1|+|E_2|-|E_3|\le 2(|V_1|+|V_2|-|V_3|)-2\)

\(G_1,G_2\)为满子图,联立上式得\(|E_3|\ge 2|V_3|-2\)

又因为\(|E_3|\le 2|V_3|-2\),所以\(|E_3|=2|V_3|-2\),进而\(|E|=2|V|-2\)

引理2\(a,b,c\)中至少存在一对点\(x,y\),使得\(G_1\)不存在满的子图同时包含\(x,y\)

反证。假设两两之间都存在一个满的子图包含它们,把这三个子图求并,由引理1得到这个并也是满的,有\(|E|=2|V|-2\)

往这个图中加入点\(u\)以及三条边,此时\(|E|=2|V|-1\),不符合。

构造方法如下:把\((x,y)\)丢入\(G_1\)中,构造出方案之后,对于\((x,y)\)所在的那个边集,把\((x,y)\)改成\((u,x),(u,y)\);另一个边集丢入剩下那条边。

接下来判定的时候,可以转化为最大权闭合非空子图问题:搞个\(n+m\)个点的图,对于一条边\(e_i=(u,v)\),连\((e_i,u),(e_i,v)\),给点赋权\(-2\),给边赋权\(1\),然后跑最大权闭合非空子图。如果跑出来结果大于\(-2\)则不合法。

因为子图要非空,直接跑只会跑到\(0\)。于是要枚举一个点,强制它选,并且将其权值变成\(0\),然后再跑最大权闭合子图。因为是二分图所以跑dinic时间是\(O(n\sqrt n m)\)

也可以用类似SDOI2014 LIS的网络流退流做法。时间是\(O(\sqrt nm+nm)\)


我的退流方法:

枚举\(i\),先判断\((i,T)\)是否为割边,如果是,强制它选,答案一样,如果不是:

\(F\)为总流量,\(f\)\((i,T)\)当前流量。将\((i,T)\)清空,跑\(flow(i,T,f)\)。新的流量可视作\(F-f+flow(i,T,f)\)

可以看做强制不走这条边,用到达这条边的流量尝试跑到\(T\)

注意搞完之后跑\(flow(T,i,f)\)回退。

int F=flow(s,t),mx=-2;
for (int i=1;i<=n;++i){
	if (!bfs(i,t))
		mx=max(mx,m-F);
	else{
		int f=rev(de[i])->c;
		de[i]->c=rev(de[i])->c=0;
		mx=max(mx,m-(F-f+flow(i,t,f))-2);
		flow(t,i,f);
		de[i]->c=2-f,rev(de[i])->c=f;
	}
	if (mx>-2)
		break;
}

人家的退流方法:

\(flow(i,S,f)\),再\(flow(S,T)\)。就是直接退掉,然后再找新的增广路,不需要考虑是否为割边。

搞完之后把边加回来,也没必要立刻重新跑一次最大流,因为下一次影响答案前会跑一次\(flow(S,T)\)

下面的代码之所以不规定\(flow(i,S)\)的流量上限,是因为\(i\)的出边只有一条,\((i,T)\)的流量等于\(flow(i,S)\)

int F=flow(s,t),mx=-2;
for (int i=1;i<=n;++i){
	de[i]->c=rev(de[i])->c=0;
	F-=flow(i,s);
	F+=flow(s,t);
	mx=max(mx,m-F-2);
	de[i]->c=2;
	if (mx>-2)
		break;
}

贴一个总的代码:

using namespace std;
#include <bits/stdc++.h>
#define N 2009
#define INF 1000000000
int n,m;
int nn,s,t;
struct EDGE{
	int to,c;
	EDGE *las;
} e[N*7*2];
int ne;
EDGE *last[N*3];
void link(int u,int v,int c){
	e[ne]={v,c,last[u]};
	last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
EDGE *de[N];
EDGE *cur[N*3];
int dis[N*3];
int S,T;
bool bfs(int s,int t){
	static queue<int> q;
	for (int i=1;i<=nn;++i)
		cur[i]=last[i];
	memset(dis,127,sizeof(int)*(nn+1));
	while (!q.empty()) q.pop();
	q.push(s);
	dis[s]=0;
	while (!q.empty()){
		int x=q.front();
		q.pop();
		for (EDGE *ei=last[x];ei;ei=ei->las)
			if (ei->c && dis[ei->to]>=INF){
				dis[ei->to]=dis[x]+1;
				if (ei->to==t)
					return 1;
				q.push(ei->to);
			}
	}
	return 0;
}
int dfs(int x,int s){
	if (x==T)
		return s;
	int have=0;
	for (EDGE *&ei=cur[x];ei;ei=ei->las)
		if (ei->c && dis[ei->to]==dis[x]+1){
			int t=dfs(ei->to,min(ei->c,s-have));
			ei->c-=t,rev(ei)->c+=t,have+=t;
			if (have==s)
				return s;
		}
	return have;
}
int flow(int _S,int _T,int lim=INF){
	S=_S,T=_T;
	memset(cur,0,sizeof(EDGE*)*(nn+1));
	int res=0;
	while (lim && bfs(S,T)){
		int t=dfs(S,lim);
		res+=t,lim-=t;
	}
	return res;
}
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	nn=n+m;
	s=++nn,t=++nn;
	ne=0;
	memset(last,0,sizeof(EDGE*)*(nn+1));
	for (int i=1;i<=n;++i)
		de[i]=e+ne,link(i,t,2),link(t,i,0);
	for (int i=1,u,v;i<=m;++i){
		scanf("%d%d",&u,&v);
		link(s,i+n,1),link(i+n,s,0);
		link(i+n,u,INF),link(u,i+n,0);
		link(i+n,v,INF),link(v,i+n,0);
	}
	int F=flow(s,t),mx=-2;
	for (int i=1;i<=n;++i){
		de[i]->c=rev(de[i])->c=0;
		F-=flow(i,s);
		F+=flow(s,t);
		mx=max(mx,m-F-2);
		de[i]->c=2;
		if (mx>-2)
			break;
	}
	if (mx>-2)
		printf("No\n");
	else
		printf("Yes\n");
	return 0;
}
posted @ 2021-04-27 22:50  jz_597  阅读(194)  评论(0编辑  收藏  举报