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;
}