【CF587D】Duff in Mafia 二分+前缀优化建图+2-SAT

【CF587D】Duff in Mafia

题意:给你一张n个点m条边的无向图,边有颜色和边权。你要从中删去一些边,满足:

1.任意两条删掉的边没有公共的顶点。
2.任意两条剩余的、颜色相同的边没有公共的顶点。
3.删去的边的边权最大值最小。

求这个最小值,并输出方案。

$n,m\le 5\times 10^4$

题解:首先二分答案。我们二分删去边权的最大值mid,则所有>mid的边都要保留,其余的可以保留也可以删去。因为每条边有删或不删两种状态,所以容易转化为2-SAT模型。于是题中条件可以转化成若干个形如这样的约束:在一个边集中,最多有一条边被删(不删)。

这是一个经典的2-SAT模型,有一个常用办法:前缀优化建图。

我们把集合中的元素排成一排,然后新建两排节点,第一排的第i个点代表在前i个元素中有一个元素被选择了,第二排的第i个点代表前i个元素中一个被选的都没有。假如某个元素选的状态是$x_i$,不选的状态是$x_i'$,对应的前缀分别是$S_i$和$S_i'$,那么:

前缀的传递:$S_{i-1}->S_i$,$S_i'->S_{i-1}'$
用当前元素更新当前前缀:$x_i->S_i,S_i'->x_i'$
用前缀来约束当前元素:$S_{i-1}->x_i',x_i->S_{i-1}'$

记住,一共6条边哦!

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#define A(_) (((_)<<1)-1)
#define B(_) ((_)<<1)
using namespace std;
const int maxn=500010;
int n,m,mid,tot,cnt,top,sum;
int to[2000010],nxt[2000010],head[maxn],dep[maxn],q[maxn],bel[maxn],low[maxn],sta[maxn],ins[maxn];
vector<int> v[maxn];
vector<int>::iterator it;
struct edge
{
	int a,b,c,d;
}p[maxn];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
inline void add(int a,int b)
{
	to[cnt]=b,nxt[cnt]=head[a],head[a]=cnt++;
}
bool cmp(const int &a,const int &b)
{
	return p[a].c<p[b].c;
}
inline void work1()
{
	int i;
	for(i=1;i<=q[0];i++)
	{
		add(A(q[i]),A(i)+tot),add(B(i)+tot,B(q[i]));
		if(i!=1)
		{
			add(A(i-1)+tot,A(i)+tot),add(B(i)+tot,B(i-1)+tot);
			add(A(i-1)+tot,B(q[i])),add(A(q[i]),B(i-1)+tot);
		}
	}
	tot+=q[0]<<1,q[0]=0;
}
inline void work2()
{
	int i;
	for(i=1;i<=q[0];i++)
	{
		add(B(q[i]),B(i)+tot),add(A(i)+tot,A(q[i]));
		if(i!=1)
		{
			add(B(i-1)+tot,B(i)+tot),add(A(i)+tot,A(i-1)+tot);
			add(B(i-1)+tot,A(q[i])),add(B(q[i]),A(i-1)+tot);
		}
	}
	tot+=q[0]<<1,q[0]=0;
}
void tarjan(int x)
{
	dep[x]=low[x]=++dep[0],ins[x]=1,sta[++top]=x;
	for(int i=head[x];i!=-1;i=nxt[i])
	{
		if(!dep[to[i]])	tarjan(to[i]),low[x]=min(low[x],low[to[i]]);
		else	if(ins[to[i]])	low[x]=min(low[x],dep[to[i]]);
	}
	if(dep[x]==low[x])
	{
		sum++;
		int t;
		do
		{
			t=sta[top--],ins[t]=0,bel[t]=sum;
		}while(t!=x);
	}
}
inline bool check()
{
	int i;
	tot=m<<1,cnt=0,memset(head,-1,sizeof(head));
	for(i=1;i<=m;i++)	if(p[i].d>mid)	add(B(i),A(i));
	for(i=1;i<=n;i++)
	{
		for(it=v[i].begin();it!=v[i].end();it++)
		{
			if(q[0]&&p[*it].c!=p[q[q[0]]].c)	work1();
			q[++q[0]]=*it;
		}
		work1();
		for(it=v[i].begin();it!=v[i].end();it++)	q[++q[0]]=*it;
		work2();
	}
	memset(dep,0,sizeof(dep)),sum=0;
	for(i=1;i<=tot;i++)	if(!dep[i])	tarjan(i);
	for(i=1;i<=tot;i+=2)	if(bel[i]==bel[i+1])	return 0;
	return 1;
}
int main()
{
	//freopen("cf587D.in","r",stdin);
	n=rd(),m=rd();
	int i,l=0,r=0,flag=0;
	for(i=1;i<=m;i++)
	{
		p[i].a=rd(),p[i].b=rd(),p[i].c=rd(),p[i].d=rd(),r=max(r,p[i].d+1);
		v[p[i].a].push_back(i),v[p[i].b].push_back(i);
	}
	for(i=1;i<=n;i++)	sort(v[i].begin(),v[i].end(),cmp);
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check())	r=mid,flag=1;
		else	l=mid+1;
	}
	if(!flag)
	{
		puts("No");
		return 0;
	}
	mid=r,check();
	tot=0;
	for(i=1;i<=m;i++)	if(bel[A(i)]>bel[B(i)])	q[++tot]=i;
	printf("Yes\n%d %d\n",r,tot);
	for(i=1;i<=tot;i++)	printf("%d ",q[i]);
	return 0;
}
posted @ 2018-03-31 13:57  CQzhangyu  阅读(464)  评论(0编辑  收藏  举报