- 给定一张\(n\)个点\(m\)条边的有向图,每条边有一种颜色和一个边权。
- 要求选出一些边,使得这些边是一个匹配,且剩下每种颜色的边也各是一个匹配。
- 求选出边最大权值的最小值,并给出一组方案。
- \(n,m\le5\times10^4\)
\(2-SAT\)
我们发现,对于一条边有两种情况:
- 如果选了它,那么所有与它有相邻端点的边都不能再选。
- 如果不选它,那么所有与它有相邻端点的与其同色的边都必须选。
\(2-SAT\)的暴力建图是非常简单的,然而这样建出来的边数是\(O(m^2)\)的,需要优化。
前缀优化建图
大概是一个\(2-SAT\)建图的套路吧。
假设我们现在是要对于一条边的选择状态,向所有与它有相邻端点的边的不选择状态连边(以其中一个端点为例)。
显然,每条含该端点的边的选择状态都会向剩余所有含该端点的边的不选择状态连边。
现在假设有\(x,y,z\)三条边,用角标\(1\)表示选择状态,角标\(0\)表示不选择状态,角标\(A,B\)分别表示向后向前连边的辅助点,则建图如下:
简单解释一下,其实每个点向后连的边都是在后面那个点的基础上加上后面那个点,向前连的边同理。应该是非常好理解的。
具体实现其实只要记一下上次的两个辅助点编号然后连边就可以了。
二分答案
然后我们发现题目是要我们最小化最大值,就是一个典型的二分答案。
对于权值大于答案的边,只要从其选择状态向不选择状态连边,就可以强制其不选了。
然后只要跑一遍\(Tarjan\)任求一组可行解即可。
代码:\(O(mlogV)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define mp make_pair
using namespace std;
int n,m;struct line {int x,y,c,v;}s[N+5];
namespace G//2-SAT
{
#define SZ 10*N
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
int cnt,oe,ee,lnk[SZ+5],h[SZ+5];struct edge {int to,nxt;}e[25*N+5];
typedef pair<int,int> Pr;int w,g[4*N+5];map<Pr,int> id;
I int ID(CI x,CI y) {return id.count(mp(x,y))?id[mp(x,y)]:(id[mp(x,y)]=++w);}//求出一种点集的编号
int d,dfn[SZ+5],low[SZ+5],tot,bl[SZ+5],T,S[SZ+5],IS[SZ+5];I void Tarjan(CI x)//Tarjan缩点
{
dfn[x]=low[x]=++d,IS[S[++T]=x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dfn[e[i].to]?
IS[e[i].to]&&Gmin(low[x],dfn[e[i].to]):(Tarjan(e[i].to),Gmin(low[x],low[e[i].to]));
if(dfn[x]==low[x]) {++tot;W(bl[S[T]]=tot,IS[S[T]]=0,S[T--]^x);}
}
I void Link(CI p,CI x,CI y)//前缀优化建边
{
cnt+=2,g[p]&&(add(x,g[p]),add(g[p]-1,y),add(g[p]-1,cnt-1),add(cnt,g[p])),//与之前辅助点之间的连边
add(x,cnt-1),add(cnt,y),g[p]=cnt;//向自己的辅助点连边,然后更新该点集最后一个辅助点
}
I void Init()//初始化图
{
w=0,cnt=m<<1;for(RI i=1,x;i<=m;++i)//两种边,每种两个端点
Link(ID(s[i].x,0),m+i,i),Link(ID(s[i].x,s[i].c),i,m+i),
Link(ID(s[i].y,0),m+i,i),Link(ID(s[i].y,s[i].c),i,m+i);
oe=ee;for(RI i=1;i<=cnt;++i) h[i]=lnk[i];
}
I bool Check(CI v)//检验答案
{
RI i;for(ee=oe,d=tot=0,i=1;i<=cnt;++i) dfn[i]=0,lnk[i]=h[i];//复制原图
for(i=1;i<=m;++i) s[i].v>v&&add(m+i,i);for(i=1;i<=cnt;++i) !dfn[i]&&(Tarjan(i),0);//在原图基础上加限制边,然后缩点
for(i=1;i<=m;++i) if(G::bl[i]==G::bl[m+i]) return 0;return 1;//如果存在两种状态在同一强连通分量中说明无解
}
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d%d%d",&s[i].x,&s[i].y,&s[i].c,&s[i].v);
G::Init();RI l=0,r=1e9+1,t;W(l^r) G::Check(t=l+r>>1)?r=t:l=t+1;if(r>1e9) return puts("No"),0;//二分答案
for(G::Check(r),puts("Yes"),t=0,i=1;i<=m;++i) G::bl[i]>G::bl[m+i]&&++t;//统计选择边数
for(printf("%d %d\n",r,t),i=1;i<=m;++i) G::bl[i]>G::bl[m+i]&&printf("%d ",i);return 0;//输出选中的边
}