Link

题意: 给一个二分图,求有多少种方案删去恰好两个点,使得最大匹配数不变。\(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

CCPC Finals 2021 H Harie Programming Contest (网络流&支配树的妙用)的更多相关文章

  1. ACM International Collegiate Programming Contest World Finals 2013

    ACM International Collegiate Programming Contest World Finals 2013 A - Self-Assembly 题目描述:给出\(n\)个正方 ...

  2. KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解

    KYOCERA Programming Contest 2021(AtCoder Beginner Contest 200) 题解 哦淦我已经菜到被ABC吊打了. A - Century 首先把当前年 ...

  3. The Ninth Hunan Collegiate Programming Contest (2013) Problem H

    Problem H High bridge, low bridge Q: There are one high bridge and one low bridge across the river. ...

  4. ACM International Collegiate Programming Contest World Finals 2014

    ACM International Collegiate Programming Contest World Finals 2014 A - Baggage 题目描述:有\(2n\)个字符摆在编号为\ ...

  5. The North American Invitational Programming Contest 2018 H. Recovery

    Consider an n \times mn×m matrix of ones and zeros. For example, this 4 \times 44×4: \displaystyle \ ...

  6. 2016 China Collegiate Programming Contest Final

    2016 China Collegiate Programming Contest Final Table of Contents 2016 China Collegiate Programming ...

  7. (寒假开黑gym)2017-2018 ACM-ICPC German Collegiate Programming Contest (GCPC 2017)

    layout: post title: (寒假开黑gym)2017-2018 ACM-ICPC German Collegiate Programming Contest (GCPC 2017) au ...

  8. (寒假GYM开黑)2018-2019 ACM-ICPC Brazil Subregional Programming Contest

    layout: post title: 2018-2019 ACM-ICPC Brazil Subregional Programming Contest author: "luowenta ...

  9. 2018-2019 ACM-ICPC Brazil Subregional Programming Contest PART (10/13)

    $$2018-2019 ACM-ICPC Brazil Subregional Programming Contest$$ \(A.Slackline\ Adventure\) \(B.Marbles ...

  10. Programming Contest Problem Types

        Programming Contest Problem Types Hal Burch conducted an analysis over spring break of 1999 and ...

随机推荐

  1. 打破联接壁垒,华为云IoT到底强在哪

    摘要:先哲说,万物莫不相异,而在今天,万物也可相通. 本文分享自华为云社区<打破联接壁垒,华为云IoT到底强在哪?>,作者:华为IoT云服务. "凡物莫不相异", 是十 ...

  2. PPT 常规设置

    高级设置 可以将撤销次数调大,最多 150次 默认拉到PPT中的图片是被压缩的,可以设置成不压缩(解压 PPT 可查看里面的图片大小) 字体嵌入 可将自动保存时间调短,默认保存目录我习惯先保存到桌面( ...

  3. 盛科交换机和ovs交换机建立VxLAN隧道

    环境信息 盛科交换机信息: R3# show version CentecOS Software, E580, Version 5.3.6 Copyright (C) 2004-2017 Centec ...

  4. 面试官:分库分表后如何生成全局ID?

    分库分表后就不能使用自增 ID 来作为表的主键了,因为数据库自增 ID 只适用于单机环境,但如果是分布式环境,是将数据库进行分库.分表或数据库分片等操作时,那么数据库自增 ID 就会生成重复 ID,从 ...

  5. 【JAVA基础】常量变量维护

    常量维护 //参与签名的系统Header前缀,只有指定前缀的Header才会参与到签名中 //换行符 private static final char LF = '\n'; public stati ...

  6. OAuth2.0回调函数用html页面转发给controller

    OAuth2.0授权中有个回调函数,就是请求授权服务后,它会将你需要的code(假设code就是你需要的数据)发送给你的回调函数(你对外开放的一个url地址用以接收参数) 注:这个回调函数有点讨厌为什 ...

  7. java中除法结果不对。

    今天遇一个非常简单地计算,计算结果居然是不对0,查了一些前辈们的资料动手实验了一下,实验结果和代码分享给大家.需要计算的公式:(7/10)*0.8 结果居然不是0.56 而是 0,最后找到原因(7/1 ...

  8. js滑动验证

    https://gitee.com/anji-plus/captcha AjCaptcha验证码: https://blog.csdn.net/zbchina2004/article/details/ ...

  9. mysql关于time时间戳相关使用

    1.当前时间: select now(); 应用: select * from game where time > now(); 2.时间的偏移: 向前偏移10天: select date_su ...

  10. KVM 管理工具:libvirt

    libvirt 简介 libvirt 是目前使用最为广泛的对 KVM 虚拟机进行管理的工具和应用程序接口.