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

Link

题意: 给一个二分图,求有多少种方案删去恰好两个点,使得最大匹配数不变。n,m2×105

二话不说先跑一遍 Dinic 网络流,设残量网络形成的图为 G
然后开始分类讨论:

1. 删去的两个点分别在两侧

设左边删去了 u,右边删去了 v。(以下称左部点为 α,右部点为 α,字母对应则互相匹配)

1.1 两边删去的均为匹配点

此时需要分别从 uv 开始找一条增广路,并且这两条路不能相交。

很开心地发现这两条路其实并不会相交,否则假设在边 (a,a) 相交,那么 u 有一条增广路 uaaxv 有一条增广路 vaay,会发现原图就会有一条增广路 xaay,与原图是最大匹配矛盾。

于是此时 uv 独立。

1.2 一边删去匹配点,一边删去未配点

不妨设 u 为匹配点,发现从 u 开始找增广路的过程中,找到的右部点均为匹配点,于是 u 不会找到 v

于是此时 uv 独立。

1.3 两边均为未配点

此时 uv 显然独立。


综上所述,u 是否合法与 v 是否合法相互独立,于是可以分开计算再乘起来即可。
以左部点 u 为例,要求其为未配点或其匹配点 u 能找到一条增广路。这等价于 G 中存在一条从 Su 的路径,直接从 S 开始搜一遍即可。右部点同理。

2. 删去的两个点在同一侧

不妨设删的都是左部点 uv注意,以下部分开始才需用到支配树。

2.1 删掉的两个均为匹配点

现在 uv 都要找到一条增广路,并且这两条路不相交。这等价于 G 中存在从 S 分别到 uv 的两条不相交的路径,等价于无法删掉某一个点能同时断掉从 Suv 的所有路径,等价于 uv 在以 S 为起点支配树上的 LCA 恰好为根(第一个等价于可以手推或用最大流最小割定理)。求出支配树然后子树内随便数一数即可。

2.2 删掉一个匹配点和一个未配点

不妨设 u 为匹配点。

此时我们要求 G 中存在一条从 Su 的路径且不经过 v这即为 v 不支配 u,也即为 v 在以 S 为起点的支配树中不是 u 的祖先。于是求出支配树还是在每个 v 的子树内数一数即可。

2.3 删掉两个未配点

trivial.


综上所述,以 S 为根跑一遍支配树然后子树计数就可以处理左部点的情况,右部点从 T 也类似跑一遍即可。注意一些连边的细节。

时间复杂度 O(mn+nlogn)

上一份代码:

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

本文作者:CharlieVinnie

本文链接:https://www.cnblogs.com/Charlie-Vinnie/p/16769073.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   CharlieVinnie  阅读(119)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起