舞动的夜晚 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个整数,升序输出这样的一hunyinhunyin对员工的认识关系的编号(他们的认识关系是在输入数据中读入的第几条认识关系)。如果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。
题解
二分图的不可行边——最大流+Tarjan
对于50%的数据,先求出最大匹配的值,然后枚举每一条边,连上之后重求最大匹配,看答案是否为原最大匹配-1。求最大匹配用匈牙利算法。
对于100%的数据,先用Dinic求任意一组最大匹配,然后建一张新图:
匹配边(i,j) j到i连边
非匹配边 (i,j) i到j连边
匹配的左点i (i,S)
不匹配的左点i (S,i)
匹配的右点j (T,j)
不匹配的右点j (j,T)然后用Tarjan求强连通分量
(i,j)是可行边的条件:
(i,j)是匹配边 或者 i,j在同一个scc里那么总边数减去可行边数就是不可行边数,即答案。
注意这个新图要包含源和汇,不能只在二分图两部之间连边,除非原最大匹配是一个完备匹配。
证明过程,写在书上了,很详细。
时间复杂度(O(Esqrt{N+M}))
注意那个源汇点连边的时候,因为初始tot=1,所以枚举的是正边。
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;
co int N=2e4+2,M=3e5;
int head[N],ver[M],edge[M],next[M],d[N],e[M];
int n,m,p,s,t,tot=1,maxflow,ans;
il void add(int x,int y,int z){
ver[++tot]=y,edge[tot]=z,::next[tot]=head[x],head[x]=tot;
ver[++tot]=x,edge[tot]=0,::next[tot]=head[y],head[y]=tot;
}
bool bfs(){
fill(d+s,d+t+1,0),d[s]=1;
queue<int> q;q.push(s);
for(int x;q.size();){
x=q.front(),q.pop();
for(int i=head[x],y;i;i=::next[i])
if(edge[i]&&!d[y=ver[i]]){
d[y]=d[x]+1,q.push(y);
if(y==t) return 1;
}
}
return 0;
}
int dinic(int x,int flow){
if(x==t) return flow;
int rest=flow;
for(int i=head[x],delta;i&&rest;i=::next[i])
if(edge[i]&&d[ver[i]]==d[x]+1){
delta=dinic(ver[i],min(rest,edge[i]));
if(!delta) d[ver[i]]=0;
edge[i]-=delta,edge[i^1]+=delta;
rest-=delta;
}
return flow-rest;
}
vector<int> a[N];
int c[N],sta[N],ins[N],dfn[N],low[N];
int scc,st,num;
il void add2(int x,int y){
a[x].push_back(y);
}
void tarjan(int x){
dfn[x]=low[x]=++num;
sta[++st]=x,ins[x]=1;
for(int i=0,y;i<a[x].size();++i){
if(!dfn[y=a[x][i]]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
++scc;int y;
do y=sta[st--],ins[y]=0,c[y]=scc; while(y!=x);
}
}
int main(){
// freopen("CH#17C.in","r",stdin);
read(n),read(m),read(p),t=n+m+1;
for(int i=1;i<=n;++i) add(s,i,1);
for(int i=1;i<=m;++i) add(i+n,t,1);
for(int i=1,x,y;i<=p;++i){
read(x),read(y);
add(x,n+y,1),e[i]=tot;
}
while(bfs())
for(int delta;delta=dinic(s,1e9);) maxflow+=delta;
for(int i=1;i<=p;++i)
edge[e[i]]?add2(ver[e[i]^1],ver[e[i]]):add2(ver[e[i]],ver[e[i]^1]);
for(int i=1;i<=n;++i) // notice: tot=1
edge[2*i]?add2(s,i):add2(i,s);
for(int i=1;i<=m;++i)
edge[2*(n+i)]?add2(n+i,t):add2(t,n+i);
for(int i=1;i<=t;++i)
if(!dfn[i]) tarjan(i);
ans=p;
for(int i=1;i<=p;++i)
ans-=edge[e[i]]||c[ver[e[i]]]==c[ver[e[i]^1]];
printf("%d
",ans);
for(int i=1;i<=p;++i)
if(!edge[e[i]]&&c[ver[e[i]]]!=c[ver[e[i]^1]]) printf("%d ",i);
puts("");
return 0;
}
BZOJ2140稳定婚姻
n对夫妻Bi和Gi。若某男Bi与某女Gj曾经交往过,他们有私奔的可能性。不妨设Bi和Gj旧情复燃,进而Bj会联系上了他的初恋情人Gk,以此递推。若在Bi和Gi离婚的前提下,这2n个人最终依然能够结合成n对情侣,那么我们称婚姻i为不安全的,否则婚姻i就是安全的。问n对夫妻的婚姻分别是安全的吗?
题解
https://www.cnblogs.com/iiyiyi/p/5876168.html
第一反应是匈牙利算法,但是太过于暴力了,过不了。
我们把夫妻中女方连向男方,旧情中男方连向女方。可以得出结论:如果该有向图的强连通分量中,夫妻双方在同一个强连通分量里,那么他们的婚姻是不安全的,否则他们的婚姻是安全的。
为什么呢?如果在同一个强连通分量中,显然可以连出一个增广路,相当于匈牙利算法可以跑,那么必定是能形成新的n对情侣的。
如果不在一个强连通分量中,可以理解为匈牙利算法不能调整了(具体原因见匈牙利算法),那么必定不能形成新的n对情侣。
为什么在一个SCC中就不稳定呢?
考虑把Bi,Gi所在的环找一个出来,所有匹配沿着环转一条边,那么得到的也是一个合法的匹配。
不在一个SCC中显然就不能这么做。
CO int N=8e4+10;
vector<int> to[N];
map<string,int> name;
int stk[N],ins[N],top;
int pos[N],low[N],dfn;
int col[N],idx;
void tarjan(int u){
stk[++top]=u,ins[u]=1;
pos[u]=low[u]=++dfn;
for(int v:to[u]){
if(!pos[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]==1) low[u]=min(low[u],pos[v]);
}
if(low[u]==pos[u]){
++idx;
do col[stk[top]]=idx,ins[stk[top]]=0;
while(stk[top--]!=u);
}
}
int main(){
int n=read<int>();
for(int i=1;i<=n;++i){
static char wife[15],husband[15];
scanf("%s%s",wife,husband);
name[wife]=i,name[husband]=i+n;
to[i].push_back(i+n);
}
int m=read<int>();
for(int i=1;i<=m;++i){
static char girl[15],boy[15];
scanf("%s%s",girl,boy);
to[name[boy]].push_back(name[girl]);
}
for(int i=1;i<=2*n;++i)if(!pos[i]) tarjan(i);
for(int i=1;i<=n;++i) puts(col[i]==col[i+n]?"Unsafe":"Safe");
return 0;
}
HAOI2017 新型城市化
Anihc 国有 (n) 座城市,城市之间存在着一些贸易合作关系,如果城市 (x) 和城市 (y) 之间存在贸易协定,那么城市 (x) 和城市 (y) 则是一对贸易伙伴(注意:((x, y)) 和 ((y, x)) 是同一对城市)。
为了实现新型城市化,实现统筹城乡一体化以及发挥城市群辐射与带动作用,Anihc 国决定规划新型城市关系。一些城市能够被称为城市群的条件是:这些城市两两都是贸易伙伴。由于 Anihc 国之前也一直很重视城市关系建设,所以可以保证在目前已存在的贸易合作关系的情况下,Anihc 的 (n) 座城市可以恰好划分为不超过两个城市群。
为了建设新型城市关系,Anihc 国想要选出两个之前并不是贸易伙伴的城市,使这两个城市称为贸易伙伴,并且要求在这两个城市称为贸易伙伴之后,最大城市群的大小至少比他们称为贸易伙伴之前的最大城市群的大小增加 (1)。
Anihc 国需要在下一次会议上讨论扩大建设新型城市关系的问题,所以需要请你求出在哪些城市之间建立贸易伙伴关系可以使得这个条件成立,即建立此关系前后的最大城市群的大小至少相差 (1)。
对于所有的数据保证:(n le 10000, 0 le m le min(150000, frac{ n(n-1) }{2}), 1 le x, y le n),保证输入的城市关系中不会出现 ((x, x)) 这样的关系,同一对城市也不会出现两次(无重边,无自环)。
题解
https://jklover.hs-blog.cf/2020/06/03/Loj-2276-新型城市化/
二分图最大匹配的必须边.
首先可以在反图上考虑这个问题(即将边集替换为其补集).
问题变为给出一张二分图,询问删掉哪些边后最大匹配会减少,即哪些边是最大匹配的必须边.
首先用最大流跑出一个最大匹配,仅保留未满流的边做 tarjan,将每个点所属的 SCC 求出.
边 ((u,v)) 是最大匹配的可行边,当且仅当它满流,或 (u,v) 在相同的 SCC 中.
证明:若其满流,显然是可行边,若在相同的 SCC 中,则可以替换掉原来的一条匹配边.
边 ((u,v)) 是最大匹配的必须边,当且仅当它满流,且 (u,v) 在不同的 SCC 中.
证明:若其不满流,显然不是必须边,若在相同的 SCC 中,则可以被其他边替换掉.
CO int N=1e4+10,inf=1e9;
vector<int> mat[N];
int col[N];
void paint(int x){
for(int y:mat[x])if(!col[y])
col[y]=3-col[x],paint(y);
}
int S,T;
struct edge {int y,c,a;};
vector<edge> to[N];
int dis[N];
IN void link(int x,int y,int c){
to[x].push_back({y,c}),to[y].push_back({x,0});
to[x].back().a=to[y].size()-1,to[y].back().a=to[x].size()-1;
}
bool bfs(){
fill(dis+1,dis+T+1,inf),dis[S]=0;
deque<int> que={S};
while(que.size()){
int x=que.front();que.pop_front();
for(CO edge&e:to[x])if(e.c and dis[e.y]==inf)
dis[e.y]=dis[x]+1,que.push_back(e.y);
}
return dis[T]<inf;
}
int dfs(int x,int lim){
if(x==T) return lim;
int rest=lim;
for(edge&e:to[x])if(e.c and dis[e.y]==dis[x]+1){
int delta=dfs(e.y,min(rest,e.c));
if(delta==0) {dis[e.y]=inf; continue;}
rest-=delta,e.c-=delta,to[e.y][e.a].c+=delta;
}
return lim-rest;
}
int pos[N],low[N],tim;
int stk[N],ins[N],top;
int scc[N],idx;
void tarjan(int x){
pos[x]=low[x]=++tim;
stk[++top]=x,ins[x]=1;
for(CO edge&e:to[x])if(e.c){
if(!pos[e.y]){
tarjan(e.y);
low[x]=min(low[x],low[e.y]);
}
else if(ins[e.y]) low[x]=min(low[x],pos[e.y]);
}
if(low[x]==pos[x]){
++idx;
do{
int u=stk[top];
scc[u]=idx,ins[u]=0;
}while(stk[top--]!=x);
}
}
int main(){
int n=read<int>();
S=n+1,T=n+2;
for(int m=read<int>();m--;){
int x=read<int>(),y=read<int>();
mat[x].push_back(y),mat[y].push_back(x);
}
for(int i=1;i<=n;++i)if(!col[i]) col[i]=1,paint(i);
for(int i=1;i<=n;++i){
if(col[i]==1){
link(S,i,1);
for(int j:mat[i]) link(i,j,1);
}
else link(i,T,1);
}
while(bfs()) dfs(S,inf);
for(int i=1;i<=T;++i)if(!pos[i]) tarjan(i);
vector<pair<int,int> > ans;
for(int x=1;x<=n;++x)if(col[x]==1)
for(CO edge&e:to[x])if(e.y<=n and e.c==0 and scc[x]!=scc[e.y])
ans.push_back({min(x,e.y),max(x,e.y)});
sort(ans.begin(),ans.end());
printf("%zd
",ans.size());
for(CO pair<int,int>&p:ans) printf("%d %d
",p.first,p.second);
return 0;
}