CCPC Finals 2021 H Harie Programming Contest (网络流&支配树的妙用)

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
posted @ 2022-10-08 15:37  CharlieVinnie  阅读(105)  评论(0编辑  收藏  举报