P10080 题解
P10080
考场上想到做法,结果忘了匈牙利和网络流,后悔莫及。但现在看来就算会了也写不出来,膜拜 @缪凌锴_Mathew 大师场切。
前置知识
二分图最大匹配,找环。
思路
题目要求找一个黑色边数量为偶数的匹配,这是一个很好的切入口,可以排除掉很多奇怪的算法。
假设现在你已经找到了一个完美匹配(找不到直接无解),称匹配中的边为原配边,此时黑色边的数量无非就两种情况:
- 黑色边的数量为偶数,此时直接输出匹配即可。
- 黑色边的数量为奇数,此时我们考虑对匹配进行调整,使黑边数量的奇偶性改变。
稍加思考可以发现,只需要找到一个环,满足环上黑边数量为奇数,且原配边占环的一半(即隔一条有一条原配边)。调整时,把环上的原配边调整为环上的非原配边即可。
如何找到这个环呢?发现在求解完美匹配后的残量网络里,恰好改变了原配边的方向。也就是说,我们只需要在残量网络中找奇环即可。
可能讲得有一点抽象,配个样例的图理解一下:
以上是残量网络:如果我们已经找到了完美匹配
需要注意的是:找环不能单纯地以每个点为根节点都 bfs 一次,这样是
如果使用 dinic 求解完美匹配的话,瓶颈在网络流,时间复杂的
代码
#include <bits/stdc++.h>
using namespace std;
#define N 1005
#define M 600005
int n,m,S,T;
int tot=1,head[N];
struct Edge
{
int next,to,w,val;
}e[M];
void add_edge(int u,int v,int w1,int w2)
{
e[++tot].next=head[u];
e[tot].to=v;
e[tot].w=w1;
e[tot].val=w2;
head[u]=tot;
}
int dep[N],now[N];
int bfs()
{
for(int i=1;i<=T;i++)
{
dep[i]=0;
}
for(int i=1;i<=T;i++) now[i]=head[i];
queue<int>q;q.push(S);dep[S]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
if(u==T) return 1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!e[i].w||dep[v]) continue;
dep[v]=dep[u]+1;
q.push(v);
}
}
return 0;
}
int dinic(int u,int flow)
{
if(u==T) return flow;
int rest=flow;
for(int i=now[u];i;i=e[i].next)
{
int v=e[i].to;
now[u]=i;
if(!e[i].w||dep[v]!=dep[u]+1) continue;
int k=dinic(v,min(rest,e[i].w));
if(!k) dep[v]=-1;
e[i].w-=k;e[i^1].w+=k;
rest-=k;
if(!rest) break;
}
return flow-rest;
}
int top,st[N];
bool vis[N][2],instk[N][2];
int match[N];
bool dfs(int u,int w)
{
if(vis[u][w]) return 0;
vis[u][w]=1;
instk[u][w]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!e[i].w||e[i].val==-1) continue;
st[++top]=i;
int ew=w^e[i].val;
if(instk[v][ew^1])
{
pair<int,int> tmp={v,ew};
while(tmp!=make_pair(v,ew^1))
{
int j=st[top--],nw=e[j^1].to;
if(nw<=n) match[nw]=j/2;
tmp=make_pair(nw,tmp.second^e[j^1].val);
}
return 1;
}
if(dfs(v,ew)) return 1;
top--;
}
instk[u][w]=0;
return 0;
}
void init()
{
tot=1;
for(int i=1;i<=T;i++) head[i]=0;
}
void solve()
{
scanf("%d%d",&n,&m);
S=n*2+1;T=n*2+2;
init();
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add_edge(x,y,1,z);
add_edge(y,x,0,z);
}
for(int i=1;i<=n;i++)
{
add_edge(S,i,1,-1);
add_edge(i,S,0,-1);
}
for(int i=n+1;i<=2*n;i++)
{
add_edge(i,T,1,-1);
add_edge(T,i,0,-1);
}
int cnt=0;
while(bfs()) cnt+=dinic(S,1e9);
if(cnt<n) return printf("-1\n"),void();
int ans=0;
for(int i=2;i<=2*m;i+=2)
{
if(!e[i].w)
match[e[i^1].to]=i/2,ans+=e[i].val;
}
if(!(ans&1))
{
for(int i=1;i<=n;i++) printf("%d ",match[i]);
putchar('\n');
return ;
}
memset(vis,0,sizeof(vis));
memset(instk,0,sizeof(instk));
for(int i=1;i<=n;i++)
{
top=0;
if(dfs(i,0))
{
for(int i=1;i<=n;i++) printf("%d ",match[i]);
putchar('\n');
return ;
}
}
printf("-1\n");
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
solve();
}
}
点个赞再走吧。