舞动的夜晚 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
i
:
匹配的右点
j
j
:
不匹配的右点
j
j
:
之后就是玄学操作:用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;
}