UOJ #210. 【UER #6】寻找罪犯 2-sat 前缀优化建边 详解



先%下Flaze_ 太强了Orz

这是一个2-sat

2-sat 边 u->v 的含义在于:若u则一定v

要诀就在于一定要对每一个这样的约束条件考虑完全


先来一个40分做法

裸的2-sat ~

常识两排点 表示i是or不是犯人

我们考虑:

若u不是罪犯:

则u说的都是真话,谈到的所有人身份都将确定

则所有说u是罪犯都是罪犯

若u是罪犯:

则u可能有一句话是假的,那么枚举这句假话,剩下的就都是真的

这样建图 跑2-sat就可以得到答案了

至于怎么求方案

就看其他的blog吧。


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

typedef double db;
typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=200100;

struct EDGE{int to,nt,val;};

int n,m;

namespace sccc
{
	EDGE e[30000000];
	int last[N],ecnt;
	inline void add(int u,int v)
	{e[++ecnt]=(EDGE){v,last[u]};last[u]=ecnt;}
	
	int dfn[N],low[N],tim;
	int bel[N],size[N],scc;
	
	bool ins[N];
	int st[N],top;
	
	void tarjan(int u)
	{
		dfn[u]=low[u]=++tim;
		st[++top]=u;ins[u]=1;
		for(int i=last[u],v;i;i=e[i].nt)
		{
			v=e[i].to;
			if(!dfn[v])
				tarjan(v),low[u]=min(low[u],low[v]);
			else if(ins[v]&&dfn[v]<low[u])
				low[u]=dfn[v];
		}
		if(dfn[u]==low[u])
		{
			int tmp;scc++;
			do
			{
				tmp=st[top--];ins[tmp]=0;
				bel[tmp]=scc;size[scc]++;
			}while(tmp!=u);
		}
	}
}

EDGE e[N];
int last[N],ecnt;
inline void add(int u,int v,int val)
{e[++ecnt]=(EDGE){v,last[u],val};last[u]=ecnt;}

int main()
{
	n=read();m=read();
	register int i,j,u,v,val;
	for(i=1;i<=m;++i)
	{
		u=read();v=read();val=read();
		add(u,v,val^1);
	}
	// 1-n real n+1-2n fake
	for(u=1;u<=n;++u)
	{
		for(i=last[u];i;i=e[i].nt)
		{
			sccc::add(u,e[i].to+(e[i].val)*n);
			//if u is real v's identity will get down
			sccc::add(e[i].to+(1^e[i].val)*n,u+n);
			//if v cannot match u u must be fake
		}
		for(i=last[u];i;i=e[i].nt)
			for(j=e[i].nt;j;j=e[j].nt)
			{
				sccc::add(e[i].to+(1^e[i].val)*n,e[j].to+e[j].val*n);
				//if e[i].to cannot match u e[j].to will surely match u 
				sccc::add(e[j].to+(1^e[j].val)*n,e[i].to+e[i].val*n);
				//ditto
			}
	}
	for(i=1;i<=n<<1;++i)
		if(!sccc::dfn[i])
			sccc::tarjan(i);
	int num=0;
	for(i=1;i<=n;++i)
		if(sccc::bel[i]==sccc::bel[i+n])
		{puts("Impossible");return 0;}
		else if(sccc::bel[i]>sccc::bel[i+n]) num++;
	cout<<num<<endl;
	for(i=1;i<=n;++i)
		if(sccc::bel[i]>sccc::bel[i+n])
			print(i),putchar(' ');
	putchar('\n');
	return 0;
}

之后是全分做法

之所以炸掉是因为边数太多

使用 前缀建边优化

我们加两排点到原来的后边

表示说这句话的人在这句话之前(包括这句话)说的话是不是都是真话

我们记这个东东为stc

所以每一条供词加入时的情况就变成了:

若这个人不是罪犯:

则之前的stc都为真,所指向的人身份正确

若这个人是罪犯(这句话为假):

则这个人之前的stc都为真,自己及之后的都为假,这个人一定是犯人,所指向的人的身份错误

若stc为真:

则指向的人身份确定,这个人之前的stc都为真

若之前stc为假:

则stc为假,这句一定为真,所指向的人身份正确

这样就可以完成所有的约束


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

typedef double db;
typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=400100;

int last[N<<1],ecnt;
struct EDGE{int to,nt;}e[N<<3];
inline void add(int u,int v)
{e[++ecnt]=(EDGE){v,last[u]};last[u]=ecnt;}

int n,m;

int dfn[N],low[N],tim;
int bel[N],size[N],scc;

bool ins[N];
int st[N],top;

void tarjan(int u)
{
	dfn[u]=low[u]=++tim;
	st[++top]=u;ins[u]=1;
	for(int i=last[u],v;i;i=e[i].nt)
	{
		v=e[i].to;
		if(!dfn[v])
			tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v]&&dfn[v]<low[u])
			low[u]=dfn[v];
	}
	if(dfn[u]==low[u])
	{
		int tmp;scc++;
		do
		{
			tmp=st[top--];ins[tmp]=0;
			bel[tmp]=scc;size[scc]++;
		}while(tmp!=u);
	}
}

// criminal 0->not 1->yes
inline int crm(int x,int p)
{return x+p*n;}

// sentence 0->real 1->fake
inline int stc(int x,int p)
{return (n<<1)+x+p*m;}

int pre[N];

int main()
{
	n=read();m=read();
	register int i,u,v,val;
	for(i=1;i<=n;++i) pre[i]=2*m+1;
	for(i=1;i<=m;++i)
	{
		u=read();v=read();val=read()^1;
		add( crm(v,val^1),stc(pre[u],0) );
		//if this sentence is fake those before must be real
		add( stc(pre[u],1),crm(v,val) );
		//opposite to before
		add( stc(i,0),crm(v,val) );
		//if the sentence and those before are real the man he refer to must match his sentence
		add( crm(v,val^1),stc(i,1) );
		//opposite to before
		add( stc(i,0),stc(pre[u],0) );
		//if the sentence and those before are real those before must be real
		add( stc(pre[u],1),stc(i,1) );
		//opposite to before
		pre[u]=i;
	}
	for(i=1;i<=n;++i)
		add( stc(pre[i],1),crm(i,1) ),
		add( crm(i,0),stc(pre[i],0) );
	for(i=1;i<=(m+n)<<1;++i)
		if(!dfn[i])
			tarjan(i);
	int num=0;
	for(i=1;i<=n;++i)
		if(bel[i]==bel[i+n])
		{puts("Impossible");return 0;}
		else if(bel[i]>bel[i+n]) num++;
	cout<<num<<endl;
	for(i=1;i<=n;++i)
		if(bel[i]>bel[i+n])
			print(i),putchar(' ');
	putchar('\n');
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值