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. 华为云GuassDB(for Redis)发布全新版本推出:Lua脚本和SSL连接加密

    摘要:9月8日,华为云GuassDB(for Redis)正式推出全新版本.新版本内核带来性能提升.无损升级.慢日志统计等多维度产品体验,同时推出Lua脚本和SSL连接加密两大重要功能,让业务设计更加 ...

  2. Google 发布:DevOps 2022现状报告

    在过去的八年中,全球超过 33,000 名专业人士参与了Accelerate State of DevOps 调查,使其成为同类研究中规模最大.运行时间最长的一项.Accelerate State o ...

  3. 高性能 Jsonpath 框架,Snack3 3.2.57 发布

    Snack3,一个高性能的 JsonPath 框架 借鉴了 Javascript 所有变量由 var 申明,及 Xml dom 一切都是 Node 的设计.其下一切数据都以ONode表示,ONode也 ...

  4. 微服务系列-如何使用 RestTemplate 进行 Spring Boot 微服务通信示例

    概述 下面我们将学习如何创建多个 Spring boot 微服务以及如何使用 RestTemplate 类在多个微服务之间进行同步通信. 微服务通信有两种风格: 同步通讯 异步通信 同步通讯 在同步通 ...

  5. .NET Moq mock internal类型

    问题 Can not create proxy for type xxx because type xxx is not accessible. Make it public, or internal ...

  6. 【django-vue】主页前端搭建 git介绍和安装 git工作流程 git常用命令 git过滤文件 重写drf方法 跨域中间件 导出项目依赖

    目录 上节回顾 1 主页前端 Header组件 Banner组件 Footer组件 2 git介绍和安装 git和svn比较 pycharm中配置git svn,git ,github,gitee,g ...

  7. Pycharm 2023 年最新激活码、破解教程,亲测有用,永久有效

    申明:本教程 Pycharm 破解补丁.激活码均收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除.若条件允许,希望大家购买正版 ! PS: 本教程最新更新时间: 2023年2月2日~ ...

  8. Educational Codeforces Round 80 A - D题题解(又是卡很久的一场比赛)

    第八场 CodeForces - 1288A. Deadline Example input 3 1 1 4 5 5 11 output YES YES NO Note In the first te ...

  9. Mynavi Programming Contest 2021(AtCoder Beginner Contest 201)A ~ E题题解

    A - Tiny Arithmetic Sequence 水题,判断3个数是否能构成等差数列 void solve() { int a, b, c; cin >> a >> b ...

  10. Python pydot与graphviz库在Anaconda环境的配置

      本文介绍在Anaconda环境中,安装Python语言pydot与graphviz两个模块的方法.   最近进行随机森林(RF)的树的可视化操作,需要用到pydot与graphviz模块:因此记录 ...