CH Round#17 舞动的夜晚(网络流+tarjan)

舞动的夜晚 CH Round #17

描述 L公司和H公司举办了一次联谊晚会。晚会上,L公司的N位员工和H公司的M位员工打算进行一场交际舞。在这些领导中,一些L公司的员工和H公司的员工之间是互相认识的,这样的认识关系一共有T对。舞会上,每位员工会尝试选择一名Ta认识的对方公司的员工作为舞伴,并且每位员工至多跳一支舞。完成的交际舞的数量越多,晚会的气氛就越热烈。顾及到晚会的气氛,员工们希望知道,哪些员工之间如果进行了交际舞,就会使整场晚会能够完成的交际舞的最大数量减小。

输入格式

第一行三个整数N、M、T。 接下来T行每行两个整数x、y,表示L公司的员工x和H公司的员工y互相认识。

输出格式

第一行一个整数cnt,表示进行了交际舞后会使整场晚会能够完成的交际舞的最大数量减小的员工有多少对。 第二行cnt个整数,升序输出这样的一对员工的认识关系的编号(他们的认识关系是在输入数据中读入的第几条认识关系)。如果cnt=0,输出一个空行。

样例输入

3 3 6
1 1
2 1
2 2
3 1
3 2
3 3

样例输出

3
2 4 5

数据范围与约定

对于50%的数据,1<=N,M<=100,1<=T<=1000
对于100%的数据,1<=N,M<=10000,1<=T<=100000,1<=x<=N,1<=y<=M

分析:
题意:给出一个二分图,询问有多少条边满足:固定了这条边必须匹配,剩下的匹配不到最大匹配

对于最大匹配问题,我们可以用网络流解决
看一个例子:
这里写图片描述

红线标出的就是最大匹配了,而蓝线中的任意一条都是题设中所说的:固定了这条边必须匹配,剩下的匹配不到最大匹配

我一看:诶这不就是没有匹配的边吗
实际上还是这个例子太特殊了

我们首先用dinic跑一遍最大流,根据流网络构建一个新图:
匹配边 (i,j) ( i , j ) j>i j − > i
非匹配边 (i,j) ( i , j ) i>j i − > j
匹配的左点 i i i>S
不匹配的左点 i i S>i
匹配的右点 j j T>j
不匹配的右点 j j j>T

之后就是玄学操作:用Tarjan求强连通分量
(i,j) ( i , j ) 是可行边的条件: (i,j) ( i , j ) 是匹配边或者 i,j i , j 在同一个 scc s c c

那么总边数减去可行边数就是不可行边数,即答案

我们可以发现,新图其实就是dinic的残量网络
很明显,对于一条匹配边,强制ta匹配对结果并无影响
用Tarjan找环,对于一个环来说,如果环中的一条边取反(匹配边变为不匹配或不匹配边匹配上),那么剩下的边也肯定可以取反调整来使整个环依然保持是个环,整个图也是没问题的
如果不是匹配边还不在一个环中,那么这个边如果强制连上,由于每一个点只能走一次(一个人只能匹配一个人),这样肯定有环遭到破坏,就达不到最大流量了

tip

纯粹看码力

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

const int INF=0x33333333;
const int N=150010;
struct node{
    int y,v,id,nxt;
};
node way[N<<1],e[N<<1];
int st[N],tot,ste[N],tet=0,n,m,s,t,T,an[N];
int deep[N];

void add(int u,int w,int z,int i) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].id=i;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].v=0;way[tot].id=i;way[tot].nxt=st[w];st[w]=tot;
}

void _add(int u,int w,int i) {
    tet++;e[tet].v=u;e[tet].y=w;e[tet].id=i;e[tet].nxt=ste[u];ste[u]=tet;
}

int bfs(int s,int t) {
    queue<int> q;
    memset(deep,-1,sizeof(deep));
    deep[s]=1; q.push(s);
    while (!q.empty()) {
        int now=q.front(); q.pop();
        for (int i=st[now];i!=-1;i=way[i].nxt)
            if (way[i].v&&deep[way[i].y]==-1) {
                deep[way[i].y]=deep[now]+1;
                q.push(way[i].y);
            }
    }
    return deep[t]!=-1;
}

int dfs(int now,int t,int limit) {
    if (now==t||!limit) return limit;
    int f,flow=0;
    for (int i=st[now];i!=-1;i=way[i].nxt)
        if (way[i].v&&deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(way[i].v,limit))))
        {
            flow+=f;
            limit-=f;
            way[i].v-=f;
            way[i^1].v+=f;
            if (!limit) break;
        }
    return flow;
}

void solve() {
    int ans=0;
    while (bfs(s,t))
        ans+=dfs(s,t,INF);
}

void rebuild() {
    for (int i=1;i<=n;i++) 
        for (int j=st[i];j!=-1;j=way[j].nxt)
            if (way[j].y>n&&way[j].y<t) {
                if (!way[j].v&&way[j^1].v)          //匹配边 
                    _add(way[j].y,i,way[j].id);
                else 
                    _add(i,way[j].y,-way[j].id);    //非匹配边 
            }
    for (int i=st[s];i!=-1;i=way[i].nxt)
        if (way[i].y>=1&&way[i].y<=n) {
            if (!way[i].v&&way[i^1].v)              //匹配点 
                _add(way[i].y,s,0);
            else 
                _add(s,way[i].y,0);
        }
    for (int i=st[t];i!=-1;i=way[i].nxt)
        if (way[i].y<t&&way[i].y>n) {
            if (way[i].v&&!way[i^1].v)              //匹配点
                _add(t,way[i].y,0);
            else
                _add(way[i].y,t,0); 
        }
}

int dfn[N],low[N],clo=0,cnt=0,belong[N];
int S[N],top=0;
bool in[N];

void dfs(int now) {
    dfn[now]=low[now]=++clo;
    S[++top]=now;
    in[now]=1;
    for (int i=ste[now];i;i=e[i].nxt)
        if (!dfn[e[i].y]) {
            dfs(e[i].y);
            low[now]=min(low[now],low[e[i].y]);
        }
        else if (in[e[i].y])
            low[now]=min(low[now],dfn[e[i].y]);
    if (low[now]==dfn[now]) {
        cnt++;
        int x=-1;
        while (x!=now) {
            x=S[top--];
            belong[x]=cnt;
            in[x]=0;
        }
    }
}

void tarjan() {
    for (int i=0;i<=t;i++)
        if (!dfn[i])
            dfs(i);

    for (int i=1;i<=tet;i++)
        if (e[i].id!=0) {                             
            if (e[i].id>0||(e[i].id<0&&belong[e[i].v]==belong[e[i].y])) continue;
            //(i,j)是匹配边或者i,j在同一个scc里
            else an[++an[0]]=-e[i].id;
        }
}

int main()
{
    memset(st,-1,sizeof(st)); tot=-1;
    scanf("%d%d%d",&n,&m,&T);
    for (int i=1;i<=T;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y+n,1,i);
    }
    s=0,t=n+m+1;
    for (int i=1;i<=n;i++) add(s,i,1,0);
    for (int i=1;i<=m;i++) add(i+n,t,1,0);
    solve();
    rebuild();
    tarjan();
    sort(an+1,an+1+an[0]);
    printf("%d\n",an[0]);
    for (int i=1;i<=an[0];i++) printf("%d ",an[i]);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值