UOJ 210 寻找罪犯 —— 2-sat + 前缀优化建边

题目链接:点我啊╭(╯^╰)╮

题目大意:

     n n n 个嫌疑人, m m m 条供词,两种供词:
     x i x_i xi y i y_i yi 是犯人, x i x_i xi y i y_i yi 不是犯人。
    注意有限制:
    每一个犯人的所有供词最多有一句是假的
    不是犯人的嫌疑人的供词总是真的
    找出所有犯人,提供任意一组可行解

解题思路:

    很明显可以用 2 − s a t 2-sat 2sat 解决
    但是如果枚举一个人可能说的假话,总共的边数是 m 2 m^2 m2
    所以考虑优化建边,注意到这里如果有一句假话,则前面和后面都是真话
    因此可以用前缀来优化建边(说出来我可能也建不出来。。。)

    设 1 − n 1-n 1n 表示第 i i i 个人不是犯人, n + 1 − 2 n n+1 - 2n n+12n 表示第 i i i 个人是犯人
     2 n + 1 − 2 n + m 2n + 1 - 2n + m 2n+12n+m 表示第 i i i 句话以及以前都是真话
     2 n + m + 1 − 2 n + 2 m 2n + m + 1 - 2n + 2m 2n+m+12n+2m 表示第 i i i 句话以及以前都是假话

    然后就可以讨论每一句话是否是假话了
    注意最后还要讨论这个人是否是犯人与讲话的情况
    这谁想的全啊

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 4e5 + 5;
int n, m, pre[maxn], ans[maxn], cnt;
int dfn[maxn], low[maxn], tot, numc;
int st[maxn], top, vis[maxn], col[maxn];
vector <int> g[maxn];
inline int check(int a, int b){return a+b*n;}
inline int work(int a, int b){return n*2+a+b*m;}
inline void add(int a, int b){g[a].push_back(b);}

void tarjan(int u){
	dfn[u] = low[u] = ++tot;
	st[++top] = u;
	vis[u] = 1;
	for(int i=0; i<g[u].size(); i++){
		int v = g[u][i];
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if(vis[v]) low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]){
		++numc;
		while(st[top+1] ^ u){
			col[st[top]] = numc;
			vis[st[top--]] = 0;
		}
	}
}

signed main() {
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) pre[i] = m * 2 + 1;
	for(int i=1, u, v, f; i<=m; i++){
		scanf("%d%d%d", &u, &v, &f);  f ^= 1;
		
		add(check(v, f^1), work(pre[u], 0));
		// 若这句话是假话,则以前都是真话 
		add(work(pre[u], 1), check(v, f));
		// 若上句话是假话,则这句话是真话 
		add(work(i, 0), check(v, f));
		// 若这句话以及之前都是真话 ,则这句话是真话 
		add(check(v, f^1), work(i, 1)); 
		// 若这句话是假话,则这句话以及以前有假话 
		add(work(i, 0), work(pre[u], 0));
		// 若这句话以及以前都是真话,则这句话以前都是真话 
		add(work(pre[u], 1), work(i, 1));
		// 若这句话以前有假话,则这句话以及以前有假话 
		
		pre[u] = i;		//	更新 u的上一句话 
	}
	for(int i=1; i<=n; i++){
		add(work(pre[i], 1), check(i, 1));
		// 若这个人以前说过假话,这个人必死
		add(check(i, 0), work(pre[i], 0));
		// 若这个人很诚实,则之前的话都是真的 
	}
	for(int i=1; i<=(n+m)<<1; i++)
		if(!dfn[i]) tarjan(i);
	for(int i=1; i<=n; i++)
		if(col[i] == col[i+n]){
			puts("Impossible");
			return 0;
		} else if(col[i] > col[i+n]) ans[++cnt] = i;
		
	printf("%d\n", cnt);
	for(int i=1; i<=cnt; i++) printf("%d ", ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值