UOJ #210. 【UER #6】寻找罪犯

Description

传送门


Solution

考虑朴素的\(2-SAT\),开点记录某个人是不是犯人,每句话是不是真的,然后每个人所说的所有话之间都要连边为保证只有一句假话。这样边数会很大,复杂度会假。

用前缀和的思想优化该做法,\(pre_{i, j}\)表示\(i\)的前\(j\)句话有没有出现假话。

那么如果第\(i\)句话是真的,就能确定对应的\(y\)的身份,反之,根据\(y\)的身份能确定\(i\)是假话。

如果之前所有说过的话有假话,那第\(i\)句一定是真话,反之,第\(i\)句是假话,则之前的一定都是真话。

如果之前所有说过的话中有假话,加上第\(i\)句话的所有话中肯定有假话,反之,如果加上第\(i\)句话的所有话中没有假话,那么去掉第\(i\)句话也没有假话。

如果第\(i\)句话是假话,那么从开头到现在的所有话中有假话,反之,如果从开头到现在都是真话,那么低\(i\)句话是真话。

然后如果一个人所有说的话中有假话,那么他是犯人,反之,他所有说过的话中肯定没有假话。

然后这样连边,边数是\(O(m)\)级别的,可以通过本题。


Code

/*
    _______                       ________                        _______
   / _____ \                     / ______ \                      / _____ \
  / /     \_\  _     __     _   / /      \ \   _     __     _   / /     \_\
 | |          | |   |  |   | | | |        | | | |   |  |   | | | |
 | |          | |   |  |   | | | |     __ | | | |   |  |   | | | |
 | |       __ \  \  |  |  /  / | |     \ \| | \  \  |  |  /  / | |       __
  \ \_____/ /  \  \/ /\ \/  /   \ \_____\  /   \  \/ /\ \/  /   \ \_____/ /
   \_______/    \___/  \___/     \______/\__\   \___/  \___/     \_______/
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

const int N = 6e5;

int cnt = 1, n, m, top, dfx, tot, num;

int head[N + 50], id[N + 50], idask[N + 50], idpre[N + 50], lst[N + 50], scc[N + 50], dfn[N + 50], st[N + 50], low[N + 50];

vector<int> crimer;

struct Node
{
	int next, to;
} edge[N * 8 + 50];

void Addedge(int u, int v)
{
//	cout << endl;
//	cout << u << " " << v << endl;
	edge[++num] = (Node){head[u], v};
	head[u] = num;
	return;
}

void Link(int u, int v)
{
	Addedge(u, v);
	Addedge(v ^ 1, u ^ 1);
	return;
}

void Tarjan(int x)
{
	dfn[x] = low[x] = ++dfx; st[++top] = x;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (!dfn[v])
		{
			Tarjan(v);
			low[x] = min(low[x], low[v]);
		}
		else if (!scc[v]) low[x] = min(low[x], dfn[v]);
	}
	if (low[x] == dfn[x])
	{
		tot++;
		while (st[top + 1] != x) scc[st[top--]] = tot;
	}
	return;
}

inline char nc(){
    #define SIZE 1000000+3
    static char buf[SIZE],*p1 = buf+SIZE,*p2 = buf+SIZE;
    if(p1 == p2){
        p1 = buf;p2 = buf+fread(buf,1,SIZE,stdin);
        if(p1 == p2) return -1;
    }
    return *p1++;
    #undef SIZE
}

template <typename T>
inline void read(T &x){
    x = 0;int flag = 0;char ch = nc();
    while(!isdigit(ch)){
        if(ch == '-') flag = 1;
        ch = nc();
    }
    while(isdigit(ch)){
        x = (x<<1) + (x<<3) + (ch^'0');
        ch = nc();
    }
    if(flag) x = -x;
}

int main()
{
	read(n); read(m);
	for (int i = 1; i <= n; i++) cnt += 2, id[i] = cnt;
	for (int i = 1; i <= m; i++) cnt += 2, idask[i] = cnt, cnt += 2, idpre[i] = cnt;
	for (int i = 1, x, y, t; i <= m; i++)
	{
		read(x); read(y); read(t);	
		if (lst[x]) Link(lst[x] ^ 1, idpre[i] ^ 1), Link(lst[x] ^ 1, idask[i]);//如果x之前说过话,那么如果这句话是假的,那么x之前说过的话都是真的;如果之前说过的话都是假的,那么加上现在的话也肯定有假的。 
		lst[x] = idpre[i];
		Link(idask[i] ^ 1, idpre[i] ^ 1);//如果这句话是假的,那么从开头到这句话都是假的。 
		if (!t) Link(idask[i], id[y] ^ 1); else Link(idask[i], id[y]);//如果这句话是真的,那么y是罪犯/好人。 
	}	
	for (int i = 1; i <= n; i++) if (lst[i]) Link(lst[i] ^ 1, id[i] ^ 1);//如果所有的话中有假的,那么i是罪犯。 
	for (int i = 2; i <= cnt; i++) if (!scc[i]) Tarjan(i); 
	for (int i = 1; i <= n; i++) if (scc[id[i]] == scc[id[i] ^ 1]) { puts("Impossible"); return 0;}
	for (int i = 1; i <= n; i++) if (scc[id[i]] > scc[id[i] ^ 1]) crimer.push_back(i);
	printf("%d\n", crimer.size());
	for (vector<int>::iterator it = crimer.begin(); it != crimer.end(); it++) printf("%d ", *it);
	return 0;
}
posted @ 2020-11-02 23:09  Tian-Xing  阅读(187)  评论(0编辑  收藏  举报