【ybt金牌导航3-6-3】【luogu P3007】奶牛议会 / The Continental Cowngress G(两种方法)

奶牛议会 / The Continental Cowngress G

题目链接:ybt金牌导航3-6-3 / luogu P3007

题目大意

有一些人,每个人对众多决案中的两个决案有表示好或不好。
必须要满足每个人两个条件的至少一个。
问你是否有合法解,如果没有判断出来。
有的话你要输出对于每个决案,它是一定要好,还是一定要不要,还是都可以。

思路

首先看着题意都知道它是 2-sat。

建图也很好搞,对于每个人的条件 A,B,那 A 的反向点就连向 B,B 的反向点就连向 A。

但这道题最难的是如何判断每个点的方案是否是一定的。
这里该出两种方法:

每次都 dfs 一次。
什么意思呢,你现在要看 i i i 是否一定要或不要,还是都可能。
那你就分别假设,假设 i i i 是要的,假设 i + n i+n i+n i i i 反向点)是要的(也就是 i i i 不要)。
然后你知道如果选了一个它后面遍历到的点都要选,那你就 dfs 遍历一遍,然后你就像判断是否有解一样,你判断一下会不会有一个点和它的反向点都被选了,没有就说明可以,否则就是不可以。
那如果两个点都可以,那就是都可能,否则的就是只能选可以的那个。

只 dfs 一次,我们从入度为 0 0 0 的点出发,然后跑 dfs。
如果你跑的时候你跑的路径上同时出现了一个点和它的反向点,那显然肯定是选后遍历到的(也就是你第二次遍历到的,也就是你发现的位置),那它就必选。
然后你知道搞一个标记,把它可以走向的点都也标记成必选就好了。
那如果两个都不是不选,那就是都可以,否则就是那个必选的。

不过要记得,这两个跑 dfs 都是在缩完点的图上进行的。
然后你会发现第二种方法一个点里面有很多个点,它们每个都有对应的点,可能在缩点后点的不同位置。
而且它们只要有一个在路径中这个点就要选,那你就搞个邻接表,把一个点里面点的对应点所在缩点后的位置都记录下来就可以了。

代码

每次 dfs 判断

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

struct node {
	int to, nxt;
}e[8001], e_[8001];
int n, m, b, c, ba, ca, tmp, tot, le[2001], KK;
int dfn[2001], low[2001], in[2001], sta[2001];
int le_[2001], KK_;
bool inn[2001];
char cc;

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK; 
}

void tarjan(int now) {
	dfn[now] = low[now] = ++tmp;
	sta[++sta[0]] = now;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (!dfn[e[i].to]) {
			tarjan(e[i].to);
			low[now] = min(low[now], low[e[i].to]);
		}
		else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
	
	if (dfn[now] == low[now]) {
		in[now] = ++tot;
		while (sta[sta[0]] != now) {
			in[sta[sta[0]]] = tot;
			sta[0]--;
		}
		sta[0]--;
	}
}

void add_(int x, int y) {
	e_[++KK_] = (node){y, le_[x]}; le_[x] = KK_;
}

void dfs(int now) {
	inn[now] = 1;
	for (int i = le_[now]; i; i = e_[i].nxt)
		if (!inn[e_[i].to]) dfs(e_[i].to);
}

int check(int x) {
	memset(inn, 0, sizeof(inn));
	dfs(in[x]);//直接从这个点开始搜一遍图
	for (int i = 1; i <= n; i++)//判断选了它会不会有矛盾产生,不会就说明可以选这个点
		if (inn[in[i]] && inn[in[i + n]]) return 0;
	return 1;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", &b);
		cc = getchar();
		while (cc != 'Y' && cc != 'N') cc = getchar();
		if (cc == 'Y') ba = 0;
			else ba = 1;
		scanf("%d", &c);
		cc = getchar();
		while (cc != 'Y' && cc != 'N') cc = getchar();
		if (cc == 'Y') ca = 0;
			else ca = 1;
		
		add((ba ^ 1) * n + b, ca * n + c);
		add((ca ^ 1) * n + c, ba * n + b);
	}
	
	for (int i = 1; i <= n + n; i++)
		if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= n; i++)
		if (in[i] == in[n + i]) {
			printf("IMPOSSIBLE");
			return 0;
		}
	
	for (int i = 1; i <= n + n; i++)
		for (int j = le[i]; j; j = e[j].nxt)
			if (in[i] != in[e[j].to])
				add_(in[i], in[e[j].to]);
	
	for (int i = 1; i <= n; i++) {
		int ck1 = check(i), ck2 = check(i + n);
		if (ck1 && ck2) printf("?");//如果两个都能选就是都行,只有一个就是一定是选这个
			else if (ck1) printf("Y");
				else printf("N");
	}
	
	return 0;
}

一次 dfs 直接全部搜出

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

struct node {
	int to, nxt;
}e[8001], e_[8001], e__[8001];
int n, m, b, c, ba, ca, tmp, tot, le[2001], KK;
int dfn[2001], low[2001], in[2001], sta[2001], ru[2001];
int le_[2001], KK_, le__[2001], KK__, mst[2001];
bool inn[2001], line[2001];
char cc;

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK; 
}

void tarjan(int now) {
	dfn[now] = low[now] = ++tmp;
	sta[++sta[0]] = now;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (!dfn[e[i].to]) {
			tarjan(e[i].to);
			low[now] = min(low[now], low[e[i].to]);
		}
		else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
	
	if (dfn[now] == low[now]) {
		in[now] = ++tot;
		while (sta[sta[0]] != now) {
			in[sta[sta[0]]] = tot;
			sta[0]--;
		}
		sta[0]--;
	}
}

void add_(int x, int y) {
	e_[++KK_] = (node){y, le_[x]}; le_[x] = KK_;
	ru[y]++;
}

void add__(int x, int y) {//这个邻接表表示这个点包含的点的反点分别在哪些点中
	e__[++KK__] = (node){y, le__[x]}; le__[x] = KK__;
}

void dfs(int now, int ans) {
	inn[now] = 1;
	line[now] = 1;
	
	if (!ans) {
		for (int i = le__[now]; i; i = e__[i].nxt)
			if (line[e__[i].to]) {//枚举这个点包含的反向点所在的点,如果和它在一条链上你就一定要选(你是后遍历到的)
				ans = 1;
				break;
			}
	}
	
	mst[now] = ans;
	
	for (int i = le_[now]; i; i = e_[i].nxt)
		if (!mst[e_[i].to]) dfs(e_[i].to, ans);//你选了之后你后面的也要跟着选,所以要把 ans 传递下去
	
	line[now] = 0;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", &b);
		cc = getchar();
		while (cc != 'Y' && cc != 'N') cc = getchar();
		if (cc == 'Y') ba = 0;
			else ba = 1;
		scanf("%d", &c);
		cc = getchar();
		while (cc != 'Y' && cc != 'N') cc = getchar();
		if (cc == 'Y') ca = 0;
			else ca = 1;
		
		add((ba ^ 1) * n + b, ca * n + c);
		add((ca ^ 1) * n + c, ba * n + b);
	}
	
	for (int i = 1; i <= n + n; i++)
		if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= n; i++)
		if (in[i] == in[n + i]) {
			printf("IMPOSSIBLE");
			return 0;
		}
	
	for (int i = 1; i <= n + n; i++)
		for (int j = le[i]; j; j = e[j].nxt)
			if (in[i] != in[e[j].to])
				add_(in[i], in[e[j].to]);
	
	for (int i = 1; i <= n; i++)
		add__(in[i], in[i + n]), add__(in[i + n], in[i]);
	
	for (int i = tot; i >= 1; i--)
		if (!ru[i]) dfs(i, 0);
	
	for (int i = 1; i <= n; i++) {
		if (!mst[in[i]] && !mst[in[i + n]]) printf("?");
			else if (!mst[in[i + n]]) printf("Y");
				else printf("N");
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值