UOJ 210 尋找罪犯 —— 2-sat + 前綴優化建邊

題目鏈接:點我啊╭(╯^╰)╮

題目大意:

    nn 個嫌疑人,mm 條供詞,兩種供詞:
    xix_iyiy_i 是犯人,xix_iyiy_i 不是犯人。
    注意有限制:
    每一個犯人的所有供詞最多有一句是假的
    不是犯人的嫌疑人的供詞總是真的
    找出所有犯人,提供任意一組可行解

解題思路:

    很明顯可以用 2sat2-sat 解決
    但是如果枚舉一個人可能說的假話,總共的邊數是 m2m^2
    所以考慮優化建邊,注意到這裏如果有一句假話,則前面和後面都是真話
    因此可以用前綴來優化建邊(說出來我可能也建不出來。。。)

    設 1n1-n 表示第 ii 個人不是犯人,n+12nn+1 - 2n 表示第 ii 個人是犯人
    2n+12n+m2n + 1 - 2n + m 表示第 ii 句話以及以前都是真話
    2n+m+12n+2m2n + m + 1 - 2n + 2m 表示第 ii 句話以及以前都是假話

    然後就可以討論每一句話是否是假話了
    注意最後還要討論這個人是否是犯人與講話的情況
    這誰想的全啊

#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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章