CCPC Finals 2021 H Harie Programming Contest (网络流&支配树的妙用)
题意: 给一个二分图,求有多少种方案删去恰好两个点,使得最大匹配数不变。\(n,m\le 2\times 10^5\)。
二话不说先跑一遍 Dinic 网络流,设残量网络形成的图为 \(G\)。
然后开始分类讨论:
1. 删去的两个点分别在两侧
设左边删去了 \(u\),右边删去了 \(v'\)。(以下称左部点为 $\alpha $,右部点为 \(\alpha '\),字母对应则互相匹配)
1.1 两边删去的均为匹配点
此时需要分别从 \(u'\) 与 \(v\) 开始找一条增广路,并且这两条路不能相交。
很开心地发现这两条路其实并不会相交,否则假设在边 \((a,a')\) 相交,那么 \(u'\) 有一条增广路 \(u'\rightarrow a\rightarrow a'\rightarrow x\),\(v\) 有一条增广路 \(v\rightarrow a'\rightarrow a\rightarrow y'\),会发现原图就会有一条增广路 \(x\rightarrow a'\rightarrow a\rightarrow y'\),与原图是最大匹配矛盾。
于是此时 \(u\) 与 \(v'\) 独立。
1.2 一边删去匹配点,一边删去未配点
不妨设 \(u\) 为匹配点,发现从 \(u\) 开始找增广路的过程中,找到的右部点均为匹配点,于是 \(u\) 不会找到 \(v'\)。
于是此时 \(u\) 与 \(v'\) 独立。
1.3 两边均为未配点
此时 \(u\) 与 \(v'\) 显然独立。
综上所述,\(u\) 是否合法与 \(v'\) 是否合法相互独立,于是可以分开计算再乘起来即可。
以左部点 \(u\) 为例,要求其为未配点或其匹配点 \(u'\) 能找到一条增广路。这等价于 \(G\) 中存在一条从 \(S\) 到 \(u'\) 的路径,直接从 \(S\) 开始搜一遍即可。右部点同理。
2. 删去的两个点在同一侧
不妨设删的都是左部点 \(u\) 和 \(v\)。注意,以下部分开始才需用到支配树。
2.1 删掉的两个均为匹配点
现在 \(u'\) 和 \(v'\) 都要找到一条增广路,并且这两条路不相交。这等价于 \(G\) 中存在从 \(S\) 分别到 \(u'\) 和 \(v'\) 的两条不相交的路径,等价于无法删掉某一个点能同时断掉从 \(S\) 到 \(u'\) 和 \(v'\) 的所有路径,等价于 \(u'\) 和 \(v'\) 在以 \(S\) 为起点支配树上的 LCA 恰好为根(第一个等价于可以手推或用最大流最小割定理)。求出支配树然后子树内随便数一数即可。
2.2 删掉一个匹配点和一个未配点
不妨设 \(u\) 为匹配点。
此时我们要求 \(G\) 中存在一条从 \(S\) 到 \(u'\) 的路径且不经过 \(v\),这即为 \(v\) 不支配 \(u'\),也即为 \(v\) 在以 \(S\) 为起点的支配树中不是 \(u'\) 的祖先。于是求出支配树还是在每个 \(v\) 的子树内数一数即可。
2.3 删掉两个未配点
trivial.
综上所述,以 \(S\) 为根跑一遍支配树然后子树计数就可以处理左部点的情况,右部点从 \(T\) 也类似跑一遍即可。注意一些连边的细节。
时间复杂度 \(O(m\sqrt{n}+n\log n)\)。
上一份代码:
Code
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
using namespace std;
// N is the number of vertices, M is the number of edges you're about to add
template<int N,int M,typename cst_type=int>
class Dinic{
public:
int n,S,T,head[N],nxt[M*2],to[M*2],tot;
cst_type cap[M*2],maxflow;
int q[N],h,t,dep[N],now[N];
bool bfs(){
h=t=0; q[t++]=S; For(i,1,n) dep[i]=0;
dep[S]=1; now[S]=head[S];
while(h<t){
int u=q[h++];
for(int e=head[u];e;e=nxt[e]){
if(cap[e]==0) continue;
int v=to[e]; if(!dep[v]){
dep[v]=dep[u]+1; now[v]=head[v]; q[t++]=v;
if(v==T) return true;
}
}
}
return false;
}
cst_type dfs(int u,cst_type flow){
if(u==T) return flow;
cst_type fw=0;
for(int& e=now[u];e;e=nxt[e]){
if(cap[e]==0) continue;
int v=to[e];
if(dep[v]!=dep[u]+1) continue;
cst_type res=dfs(v,min(flow,cap[e]));
if(res==0) { dep[v]=0; continue; }
cap[e]-=res; cap[e^1]+=res; fw+=res; flow-=res;
if(flow==0) break;
}
return fw;
}
public:
void init(int _n) { n=_n; memset(head,0,sizeof(head)); tot=1; }
int add_edge(int x,int y,cst_type z) {
nxt[++tot]=head[x]; head[x]=tot; to[tot]=y; cap[tot]=z;
nxt[++tot]=head[y]; head[y]=tot; to[tot]=x; cap[tot]=0; return tot-1;
}
cst_type solve(int _S,int _T,cst_type inf=numeric_limits<cst_type>::max()) {
S=_S; T=_T; maxflow=0;
while(bfs()) { cst_type res=-1; while((res=dfs(S,inf))!=0) maxflow+=res; }
return maxflow;
}
bool is_full(int e) { return cap[e]==0; }
cst_type get_flow(int e) { return cap[e^1]; }
};
template<int N>
class DominatorTree{
int n,dfn[N],raw[N],dfscnt,semi[N],fa[N],pa[N],ffa[N][18],dep[N],in[N];
vector<int> to[N],from[N],cp[N],cv[N];
void dfs(int u){
raw[dfn[u]=++dfscnt]=u; for(int v:to[u]) if(!dfn[v]) { cp[u].push_back(v); pa[v]=u; dfs(v); }
}
int getfa(int x){
if(x!=fa[x]) { int t=getfa(fa[x]); semi[x]=min(semi[x],semi[fa[x]]); fa[x]=t; } return fa[x];
}
int lca(int x,int y){
if(x==0||y==0) return x^y;
if(dep[x]<dep[y]) swap(x,y);
Rev(i,17,0) if(dep[ffa[x][i]]>=dep[y]) x=ffa[x][i];
if(x==y) return x;
Rev(i,17,0) if(ffa[x][i]!=ffa[y][i]) { x=ffa[x][i]; y=ffa[y][i]; }
return ffa[x][0];
}
public:
void init(int _n) { n=_n; }
void add_edge(int x,int y) { to[x].push_back(y); from[y].push_back(x); }
void solve(int S,int* ans){
dfs(S); //assert(dfscnt==n);
For(i,1,n) { fa[i]=i; semi[i]=dfn[i]; }
Rev(i,dfscnt,2){
int u=raw[i]; if(!dfn[u]) continue;
for(int w:from[u]) if(dfn[w]) { getfa(w); semi[u]=min(semi[u],semi[w]); }
fa[u]=pa[u]; cp[raw[semi[u]]].push_back(u); // Must do it right now!
}
For(u,1,n) for(int v:cp[u]) { cv[v].push_back(u); in[v]++; }
static int q[N],h,t; h=t=0; q[t++]=S;
while(h<t){
int u=q[h++]; ans[u]=0;
for(int v:cv[u]) if(ans[u]==0) ans[u]=v; else ans[u]=lca(ans[u],v);
dep[u]=dep[ffa[u][0]=ans[u]]+1; For(i,1,17) ffa[u][i]=ffa[ffa[u][i-1]][i-1];
for(int v:cp[u]) if((--in[v])==0) q[t++]=v;
}
}
};
const int N=2e5+5; typedef long long ll;
Dinic<N*2,N*2,int> G;
DominatorTree<N*2> T1,T2; ll ans;
int n,m,S,T,col[N],cp[N],ed[N],visS[N],visT[N],fa[N],cnt[N],Scnt,Tcnt;
vector<int> to[N],from[N],clis[N],son[N];
void dfs(int u){
for(int v:to[u]) if(!col[v]) { col[v]=3-col[u]; dfs(v); }
}
void dfsS(int u) { visS[u]=1; if(col[u]==2) Scnt++; for(int v:to[u]) if(!visS[v]) dfsS(v); }
void dfsT(int u) { visT[u]=1; if(col[u]==1) Tcnt++; for(int v:from[u]) if(!visT[v]) dfsT(v); }
void dp1(int u){
cnt[u]=(col[u]==2&&visS[u]);
for(int v:son[u]) { dp1(v); cnt[u]+=cnt[v]; }
if(col[u]==1&&!cp[u]) ans+=Scnt-cnt[u];
}
void dp2(int u){
cnt[u]=(col[u]==1&&visT[u]);
for(int v:son[u]) { dp2(v); cnt[u]+=cnt[v]; }
if(col[u]==2&&!cp[u]) ans+=Tcnt-cnt[u];
}
ll C2(int x) { return 1ll*x*(x-1)/2; }
signed main(){
cin>>n>>m; For(i,1,m) { int x,y; cin>>x>>y; to[x].push_back(y); to[y].push_back(x); }
For(i,1,n) if(!col[i]) { col[i]=1; dfs(i); }
G.init(n+2); S=n+1; T=n+2;
For(u,1,n){
if(col[u]==1) { ed[u]=G.add_edge(S,u,1); for(int v:to[u]) clis[u].push_back(G.add_edge(u,v,1)); }
else ed[u]=G.add_edge(u,T,1);
}
G.solve(S,T);
For(u,1,n){
if(col[u]==1){
if(G.is_full(ed[u])){
int sz=to[u].size();
For(i,0,sz-1) if(G.is_full(clis[u][i])) { cp[u]=to[u][i]; cp[to[u][i]]=u; break; }
}
}
}
For(u,1,n+2) to[u].clear();
for(int e=2;e<=G.tot;e+=2){
if(G.cap[e]) { to[G.to[e^1]].push_back(G.to[e]); from[G.to[e]].push_back(G.to[e^1]); }
else { to[G.to[e]].push_back(G.to[e^1]); from[G.to[e^1]].push_back(G.to[e]); }
}
dfsS(S); dfsT(T); ll t1=0,t2=0;
For(i,1,n){
if(col[i]==1) { if(!cp[i]||visS[cp[i]]) t1++; }
else { if(!cp[i]||visT[cp[i]]) t2++; }
}
ans=t1*t2;
T1.init(n+2); For(u,1,n+2) for(int v:to[u]) T1.add_edge(u,v);
T1.solve(S,fa); For(u,1,n+2) son[fa[u]].push_back(u);
dp1(S); int o1=0; For(i,1,n) if(col[i]==1&&!cp[i]) o1++; ans+=C2(o1);
ans+=C2(cnt[S]); for(int v:son[S]) ans-=C2(cnt[v]);
For(i,1,n+2) { fa[i]=cnt[i]=0; son[i].clear(); }
T2.init(n+2); For(u,1,n+2) for(int v:to[u]) T2.add_edge(v,u);
T2.solve(T,fa); For(u,1,n+2) son[fa[u]].push_back(u);
dp2(T); int o2=0; For(i,1,n) if(col[i]==2&&!cp[i]) o2++; ans+=C2(o2);
ans+=C2(cnt[T]); for(int v:son[T]) ans-=C2(cnt[v]);
cout<<ans<<'\n';
cerr<<"Time = "<<clock()<<" ms\n";
return 0;
}
// START TYPING IF YOU DON'T KNOW WHAT TO DO