首页 > 其他分享 >CCPC Finals 2021 H Harie Programming Contest (网络流&支配树的妙用)

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

时间:2022-10-08 15:45:20浏览次数:81  
标签:Harie semi Contest int Programming fa cp col rightarrow

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

标签:Harie,semi,Contest,int,Programming,fa,cp,col,rightarrow
From: https://www.cnblogs.com/Charlie-Vinnie/p/16769073.html

相关文章

  • MSU Trinity Contest Petrozavodsk Winter Camp 2014
    题目列表A.MEX-QueryD.ShortEnoughTaskF.JustAnotherSequenceProblemA.ABBA题意:Solution思路:CodeconstintN=1000010;intn,......
  • The 2021 ICPC Asia Shenyang Regional Contest
    比赛链接:https://codeforces.com/gym/103427B.BitwiseExclusive-ORSequence题意:给定\(m\)个限制,要求构造一个全是非负整数的序列\(a\),每个限制告诉\(u,v,w\),......
  • 「题解」Codeforces GYM 102268 J Jealous Split(300iq Contest 1 J)
    怎么想到的结论?结论是,如果把看成最小化\(\sum{s_i}^2\),那么一定满足条件。证明是考虑如果相邻两段\(s>t\),如果不满足条件即\(s-t>\max\),说明将\(s\)和\(t\)交界处......
  • AtCoder Beginner Contest 271
    咕咕咕咕。E-SubsequencePath最短路问题变种,Dijkstra最短路改改就行了。AC代码//Problem:E-SubsequencePath//Contest:AtCoder-KYOCERAProgrammingC......
  • AtCoder Regular Contest 149
    ARC149A-RepdigitNumber符合条件的数一共只有\(9N\)个,随便怎么做都行。ACCodeARC149B-TwoLISSum这个操作相当于我们可以将\(A\)任意排列,然后对\(B\)进行......
  • AtCoder Regular Contest 149(持续更新)
    Preface最近国庆在外面玩的有点high啊,欠了一篇AT和两篇CF没写,今天先浅浅写一下这场当时10.2号在外面玩得有点晚了,到寝室刚好比赛开始,晚饭都没吃就开干了主要是C写的太久......
  • AtCoder Regular Contest 149
    A发现所有数字都相同的数一共只有\(10^6\)种,考虑枚举每种情况,关键在于如何判断一个数\(\bmodm\)是否为\(0\)。考虑\(9\bmod8=1\),而\(99\bmod8=(9\times10+9)\b......
  • AtCoder Beginner Contest 271
    AtCoderBeginnerContest271D-FlipandAdjust一共有\(N\)张牌,每一面都写着一个整数。卡\(i(1\lei\leN)\)前面写着整数\(a_i\),后面写着整数\(b_i\)。你可......
  • docker: Error response from daemon: driver failed programming external connectiv
    [root@localhost~]#dockerrun-itd--namemysql-test-p3306:3306-eMYSQL_ROOT_PASSWORD=123456mysqlb2796013b95cdf82a8bfc29f2caaf46106b09f7cbf1125338008b3f7......
  • AtCoder Regular Contest 137 D
    一道很好的题目,运用了很多不同的技巧。结论1:枚举变换次数\(k\),若\(A_{i}\)对答案有贡献,当且仅当\(C_{n-i+k-1}^{k-1}\equiv1\mod2\)。首先我们可以统计\(A_{p}\)对......