jzoj4350. 【WC2016模拟】废水回收(gates)

Description

在这里插入图片描述

Sample Input

Input1:
3 2
1 0 2 1
1 0 2 0
1 1 2 1

Input2:
2 1
1 0 1 0
1 1 1 1

Sample Output

Output1:
0
1

Output2:
IMPOSSIBLE

Data Constraint

在这里插入图片描述

题解

此题稍微运用到一点点的2-SAT知识。

前置芝士:2-SAT
康这里!

现在我们知道了2-SAT,那看看这题怎么用。

分析原题题意:有很多个水阀,然后水阀有两个阀门,只有关掉一个阀门,水阀就会关闭。然后这两个阀门又分别对应着一个开关,开关的方式和上面的一样。

考虑2-SAT模型!
先把每个开关拆成两个点,一个表示开启,一个表示关闭。
然后再看到阀门,我们发现,任意两个阀门必须要有一个开启,所以我们可以连边。
连完边之后就会出现环的情况,于是我们就可以用tarjan把这个环缩掉。
当然,这时候还需要判断是不是某个开关的两个点都在同一个环中,如果是就无解。
缩完点后就变成了一个dag。建立反向边,直接跑一边拓补序即可。
当然,还要判断开关两个点在不同链的情况,要舍掉其中一个。

代码

#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))

using namespace std;

int n,m,q,g;
int final[2000001],b[1000001],nex[1000001];
int fir[2000001],c[1000001],las[1000001];
int dfn[2000001],low[1000001],st[1000001],bz[1000001];
int l[1000001],r[1000001],a[1000001],f[1000001];
int ans[1000001],deg[1000001],d[1000001];

void link(int x,int y)
{
	b[++q]=y;
	nex[q]=final[x];
	final[x]=q;
}

void dg(int x)
{
	dfn[x]=low[x]=++g;
	st[++st[0]]=x;
	bz[x]=1;
	
	for(int j=final[x];j;j=nex[j])
		if(!dfn[b[j]])
		{
			dg(b[j]);
			low[x]=min(low[x],low[b[j]]);
		}
		else if(bz[b[j]])low[x]=min(low[x],dfn[b[j]]);
	
	if(low[x]==dfn[x])
	{
		l[x]=q+1;
		for(;st[st[0]]!=x;st[0]--)
		{
			int u=st[st[0]];
			bz[u]=0,a[++q]=u,f[u]=x;
		}
		
		bz[x]=0,a[++q]=x,f[x]=x;
		st[0]--;
		r[x]=q;
	}
}

int pd(int x)
{
	for(int i=l[x];i<=r[x];i++)
		if(ans[(a[i]-1)%m+1]>=0)return 1;
	return 0;
}

void get(int x)
{
	for(int i=l[x];i<=r[x];i++)
		ans[(a[i]-1)%m+1]=(a[i]-1)/m;
}

void solve()
{
	int t=1,w=0;
	for(int i=1;i<=2*m;i++)
		if((f[i]==i)&&(!deg[i]))d[++w]=i;
	
	for(;t<=w;t++)
	{
		int x=d[t];
		
		if(pd(x))continue;
		get(x);
		
		for(int j=fir[x];j;j=las[j])
		{
			deg[c[j]]--;
			if(!deg[c[j]])d[++w]=c[j];
		}
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	
	memset(final,0,sizeof(final));
	for(int i=1;i<=n;i++)
	{
		int u,v,x,y;
		scanf("%d%d%d%d",&u,&x,&v,&y);
		
		link(u+(x^1)*m,v+y*m);
		link(v+(y^1)*m,u+x*m);
	}
	
	memset(dfn,0,sizeof(dfn));
	memset(bz,0,sizeof(bz));
	st[0]=0;g=0;
	q=0;
	
	for(int i=1;i<=2*m;i++)
		if(!dfn[i])dg(i);
	
	for(int i=1;i<=m;i++)
		if(f[i]==f[i+m])
		{
			printf("IMPOSSIBLE\n");
			return 0;
		}
	
	memset(fir,0,sizeof(fir));
	memset(deg,0,sizeof(deg));
	q=0;
	for(int i=1;i<=2*m;i++)
		for(int j=final[i];j;j=nex[j])
			if(f[i]!=f[b[j]])
			{
				c[++q]=f[i];
				las[q]=fir[f[b[j]]];
				fir[f[b[j]]]=q;
				
				deg[f[i]]++;
			}
	
	memset(ans,128,sizeof(ans));
	solve();
	
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值