题意:给一张无向图,判断能否分成两个生成森林。
n ≤ 2 × 1 0 3 , m ≤ 4 × 1 0 3 n\leq 2\times 10^3,m\leq 4\times 10^3 n≤2×103,m≤4×103
题目中这样的图称为“丛林”,下面以此来简称。
结论 一张图是丛林的充要条件是它的每一个子图 G = ( ∣ V ∣ , ∣ E ∣ ) G=(|V|,|E|) G=(∣V∣,∣E∣) 有 ∣ E ∣ ≤ 2 ∣ V ∣ − 2 |E|\leq 2|V|-2 ∣E∣≤2∣V∣−2
必要性显然。
充分性:考虑归纳法,当 ∣ 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∣=2∣V∣−2 的图,对于当前图的两个交非空的子图 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∣=2∣V1∣−2,∣E2∣=2∣V2∣−2,∣E3∣≤2∣V3∣−2,所以 ∣ E ∣ ≥ 2 ∣ V ∣ − 2 |E|\geq 2|V|-2 ∣E∣≥2∣V∣−2。又因为 ∣ E ∣ ≤ 2 ∣ V ∣ − 2 |E|\leq 2|V|-2 ∣E∣≤2∣V∣−2,所以 ∣ E ∣ = 2 ∣ V ∣ − 2 |E|=2|V|-2 ∣E∣=2∣V∣−2。得证。
引理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)∣=2∣V(F)∣−2。我们加入 u u u 和这三条边,就得到了一张 ∣ E ∣ = 2 ∣ V ∣ − 1 |E|=2|V|-1 ∣E∣=2∣V∣−1 的图,它是原图的子图,矛盾,得证。
我们在 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 ∣E∣≤2∣V∣−2
∣ E ∣ − 2 ∣ V ∣ ≤ − 2 |E|-2|V|\leq -2 ∣E∣−2∣V∣≤−2
就是最大权闭合子图对于每条边建一个点,从 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 m−c≤−2
然后你会发现至少有一个大小为
m
m
m 的割,所以会永远输出 No
。
冷静分析,这样的原因是空图的存在。所以我们要枚举一个点强制选,也就是断掉对应的边,最后最小割
+
2
+2
+2,当
c
−
2
<
m
c-2<m
c−2<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;
}