Description
给定一张 (n) 个点 (m) 条边的无向图,每条边有一个颜色 (c) 和权值 (t)。
你要选出一些边,使得它们是一个匹配,同时剩下的边每种颜色也是一个匹配。
同时,你要最小化选出的边的最大权值。
(n,m≤5 imes 10^4)
Solution
首先发现是个在值域上的存在性问题,所以二分最大可以小可以选的边权
首先发现这里的匹配就是如果两个颜色一样的边连到了相同的节点上面,那么就发生了冲突
两条边的存在性就是 (i||j)
同时还要维护一个权值是 (0) 的边集也得满足匹配的条件
那么发现这东西大概是个 (2-sat) 问题
建图
(1.) 对于每个点,连向这个所有边连 (i o (!j))
(2.) 对于同一个节点上的每条边,如果颜色一样,那么就连上 ((!i) o j)
(3.) 对于边权大的边,先连上 (i o (!i))
然后发现边太多了
然后是 (2-sat) 问题里面比较经典的一个做法:前后缀优化建图
具体做法就是 (1dots i) 缩成一个点 (s_i) 然后 ((!1)dots (!i)) 缩成 (!s_i)
(这里和线段树优化建图不太一样的方面是这个做法更多适应完全图)
具体而言就是
(1.i o s_i,(!s_i) o (!i))
(2.s_i o s_{i+1},(!s_i) o (!s_{i-1}))
(3.s_i o [!(i+1)],i o [!s_{i+1}])
这是对于选出来的边是匹配的限制的建边方式,每种颜色剩下的是一个匹配的方案就是把这些边反过来
写的时候注意的是先把所有的第一种和第二种边建出来,然后每次只用加上第一种边就好了
(不太好写)
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define reg register
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=5e5+10;
struct node{
int val,col;
}e[N<<1];
vector<int> g[N],vec[N],tmp;
int st[N],top,tim,tot,dfn[N],mx,low[N],id[N],scc,n,m;
bool vis[N];
inline void tarjan(int x)
{
low[x]=dfn[x]=++tim; vis[x]=1; st[++top]=x;
int sz=vec[x].size();
for(reg int i=0;i<sz;++i)
{
int t=vec[x][i];
if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]);
else if(vis[t]) low[x]=min(low[x],dfn[t]);
}
if(low[x]==dfn[x])
{
++scc;
do{
vis[st[top]]=0;
id[st[top]]=scc;
top--;
}while(st[top+1]!=x);
}return;
}
inline bool check(int x)
{
for(reg int i=1;i<=m;++i) if(e[i].val>x) vec[i].push_back(i+m);
for(reg int i=1;i<=tot;++i) dfn[i]=low[i]=id[i]=st[i]=vis[i]=0;
tim=scc=top=0;
for(reg int i=1;i<=tot;++i) if(!dfn[i]) tarjan(i);
bool fl=1;
for(reg int i=1;i<=m;++i) if(id[i]==id[i+m]){fl=0; break;}
for(reg int i=1;i<=m;++i) if(e[i].val>x) vec[i].pop_back();
return fl;
}
inline void build1(vector<int> now)
{
int sz=now.size(),S1=-1,S2=-1;
for(reg int i=0;i<sz;++i)
{
int s1=++tot,s2=++tot;
vec[now[i]].push_back(s1); vec[s2].push_back(now[i]+m);
if(S1!=-1)
{
vec[S1].push_back(s1),vec[s2].push_back(S2);
vec[S1].push_back(now[i]+m); vec[now[i]].push_back(S2);
}S1=s1,S2=s2;
} return;
}
inline void build2(vector<int> now)
{
int sz=now.size(),S1=-1,S2=-1;
for(reg int i=0;i<sz;++i)
{
int s1=++tot,s2=++tot;
vec[s1].push_back(now[i]); vec[now[i]+m].push_back(s2);
if(S1!=-1)
{
vec[s1].push_back(S1); vec[S2].push_back(s2);
vec[now[i]+m].push_back(S1); vec[S2].push_back(now[i]);
} S1=s1; S2=s2;
} return ;
}
inline bool cmp(int x,int y){return e[x].col<e[y].col;}
signed main()
{
n=read(); m=read();
for(reg int i=1,u,v,w,c;i<=m;++i)
{
u=read(),v=read();
e[i].col=read(),e[i].val=read();
mx=max(e[i].val,mx);
g[u].push_back(i),g[v].push_back(i);
} tot=m<<1;
for(reg int i=1;i<=n;++i)
{
sort(g[i].begin(),g[i].end(),cmp);
build1(g[i]);
int sz=g[i].size();
for(reg int j=0;j<sz;++j)
{
int nxt=j; while(nxt<sz&&e[g[i][nxt]].col==e[g[i][j]].col) tmp.push_back(g[i][nxt]),++nxt;
build2(tmp); tmp.clear(); j=nxt-1;
}
}
if(!check(mx)) return puts("No"),0;
puts("Yes");
int l=0,ans,r=mx,count=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
for(reg int i=1;i<=m;++i) if(e[i].val>ans) vec[i].push_back(i+m);
for(reg int i=1;i<=tot;++i) dfn[i]=low[i]=st[i]=vis[i]=0;
tim=scc=top=0;
for(reg int i=1;i<=m;++i) if(!dfn[i]) tarjan(i);
for(reg int i=1;i<=m;++i) if(id[i]<id[i+m]) ++count;
printf("%lld %lld
",ans,count);
for(reg int i=1;i<=m;++i) if(id[i]<id[i+m]) printf("%lld ",i);
puts("");
return 0;
}
}
signed main(){return yspm::main();}