P10940 舞动的夜晚题解

前置芝士: 最大流 & 二分图匹配

原题链接:P10940 舞动的夜晚

本题题意:给出一张二分图,求该图最大匹配的必不经边


定理1:对于原二分图中任意边\((x,y)\),若\((x,y)\)为匹配边,并且在残量网络中分属不同强连通分量,则\((x,y)\)为必经边。

定理2:对于原二分图中任意边\((x,y)\),若\((x,y)\)为匹配边 \((x,y)\)在残量网络中属于同一强连通分量,则\((x,y)\)为可行边。

推论:

对于原二分图中任意边\((x,y)\),若\((x,y)\)为非匹配边 在残量网络中分属不同强连通分量,则\((x,y)\)为必不经边。


对于上述定理,我们进行简单证明:

对于定理1,若\((x,y)\)属于同一强连通分量,那么我们必然可以找到一条由非匹配边构成的由\(x\)到\(y\)路径,若我们将路径中由左部点到右部点的边替换原匹配中的匹配边,显然仍然是最大匹配,这与其为必经边矛盾,故原定理得证。

对于定理2,用类似方法同样可以证明,这里不再赘述。

显然,必不经边的集合是可行边在原二分图边集中的补集所以上面我们提出的推论正确。


那么本题做法就显而易见了,求出任意最大匹配,对求出最大匹配后的残量网络求连通分量,对于每一条原二分图上的边根据上述推论判断即可。

代码:

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4+10,M=4e5+10,inf=LONG_LONG_MAX;
int h[N],to[M],nx[M],vl[M],idx;
int now[N],d[N];
int dfn[N],low[N],fa[M];
bool in[N];
int sc[N],scc;
int res[M],cnt;
int tn,st[N],top;
int n,m,s,t,T;
queue<int> q;

bool bfs()
{
    memset(d,0,sizeof(d));
    while(q.size()) q.pop();
    d[s]=1,now[s]=h[s];
    q.push(s);
    while(q.size())
    {
        int u=q.front();
        q.pop();
        for(int i=h[u];~i;i=nx[i])
        {
            int v=to[i];
            if(d[v] || !vl[i]) continue;
            d[v]=d[u]+1;
            now[v]=h[v];
            q.push(v);
            if(v==t) return 1;
        }
    }
    return 0;
}

int dinic(int u,int flow)
{
    if(u==t) return flow;
    int res=flow,k=0;
    for(int i=now[u];~i && flow;i=nx[i])
    {
        int v=to[i];
        now[u]=i;
        if(d[v]==d[u]+1 && vl[i])
        {
            k=dinic(v,min(res,vl[i]));
            if(!k) d[v]=0;
            vl[i]-=k;
            vl[i^1]+=k;
            res-=k;
        }
    }
    return flow-res;
}

void add(int u,int v,int w)
{
    nx[idx]=h[u];
    to[idx]=v;
    vl[idx]=w;
    h[u]=idx++;
}

void tarjan(int u)
{
    dfn[u]=low[u]=++tn;
    st[++top]=u;
    in[u]=1;
    for(int i=h[u];~i;i=nx[i])
    {
        if(!vl[i]) continue;
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(in[v]) low[u]=min(low[u],dfn[v]);
    }

    if(dfn[u]==low[u])
    {
        scc++;
        int x;
        do
        {
            x=st[top--];
            in[x]=0;
            sc[x]=scc;
        } while(x!=u);
    }
}

signed main(){
    memset(h,-1,sizeof(h));
    cin>>n>>m>>T;
    for(int i=1;i<=T;i++)
    {
        int u,v;
        cin>>u>>v;
        v+=n;
        fa[idx]=u;
        add(u,v,1);
        fa[idx]=v;
        add(v,u,0);
    }

    s=0,t=n+m+1;
    for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);
    for(int i=1;i<=m;i++) add(i+n,t,1),add(t,i+n,0);

    int ans=0;
    while(bfs())
    {
        int flow=0;
        while(flow=dinic(s,inf)) ans+=flow;
    }

    for(int i=1;i<=n+m;i++) if(!dfn[i]) tarjan(i);
    for(int i=0;i<=idx;i++)
    {
        int u=fa[i],v=to[i];
        if(u==0 || v==n+m+1) break;
        if(i%2==1) continue;
        if(sc[u]!=sc[v] && vl[i]) res[++cnt]=ceil((i+1)/2.0);
    }//利用链式前向星成对变换性质找到原二分图上的边
    cout<<cnt<<'\n';
    for(int i=1;i<=cnt;i++) cout<<res[i]<<" ";
    cout<<'\n';
}