[UOJ210]寻找罪犯

2-sat神题。。

告诉是2-sat我也完全想不到正解。

看了看题解其实一步步分析也不算很难

这个题首先是要围绕每个人是否是犯人和每句话是否是真话来思考

首先要明确的是:

1.好人不说谎话

2.说了谎话的只能是坏人

所以我们就知道了一组对称的限制条件:

好人->之前没说过谎话

之前说过谎话->坏人

如何判断一句话是否是谎话?

这个人说的话和事实不相符,这也是一种限制条件

这时我们发现还有一个很重要的条件:哪怕是坏人,也最多只说一句谎话

所以如果当前是谎话,那这个人之前所说的所有话和之后所说的所有话都是真话

所以我们在每个点应该存的是在这个点之前(包括该点)有没有说过谎话

利用上面这些限制,就可以做出这道题啦~

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #define M 1000010
 5 using namespace std;
 6 struct point{
 7     int to,next;
 8 }e[M<<1];
 9 int n,m,num,cnt,tim,tot,top;
10 int head[M],dfn[M],low[M],st[M],ans[M],co[M],pre[M];
11 bool vis[M];
12 int is_crm(int a,int b) {return a+b*n;}
13 //0->not
14 //1->yes
15 int word(int a,int b) {return (n<<1)+a+b*m;}
16 //0->true
17 //1->false
18 void add(int from,int to)
19 {
20     e[++num].next=head[from];
21     e[num].to=to;
22     head[from]=num;
23 }
24 void tarjan(int x)
25 {
26     dfn[x]=low[x]=++tim;
27     st[++top]=x;
28     vis[x]=true;
29     for(int i=head[x];i;i=e[i].next)
30     {
31         int to=e[i].to;
32         if(!dfn[to])
33         {
34             tarjan(to);
35             low[x]=min(low[x],low[to]);
36         }
37         else if(vis[to]) low[x]=min(low[x],low[to]);
38     }
39     if(low[x]==dfn[x])
40     {
41         tot++;
42         while(st[top+1]!=x)
43         {
44             co[st[top]]=tot;
45             vis[st[top]]=false;
46             top--;
47         }
48     }
49 }
50 int main()
51 {
52     scanf("%d%d",&n,&m);
53     for(int i=1;i<=n;i++) pre[i]=2*m+1;
54     for(int i=1;i<=m;i++)
55     {
56         int x,y,opt; scanf("%d%d%d",&x,&y,&opt);
57         opt^=1;
58         add(is_crm(y,opt^1),word(pre[x],0));
59         //如果事实和他说的不是一回事,那他说的就是假话 
60         //如果这句话是假话,在此之前的所有话皆为真话
61         add(word(pre[x],1),is_crm(y,opt));
62         //如果这个人之前说过假话了,那么这句话就是真话
63         //他说这个人是什么就是什么
64         add(word(i,0),is_crm(y,opt));
65         //如果这句话和之前都是真话,他说这个人是什么就是什么
66         add(is_crm(y,opt^1),word(i,1));
67         //这句话说的是反话,那这句话及之前的所有话中有反话
68         add(word(i,0),word(pre[x],0));
69         //如果这句话及之前的所有话皆为真,那么这句话之前所有话皆为真
70         add(word(pre[x],1),word(i,1));
71         //如果之前有假的,这句话及之前的话也有假的
72         pre[x]=i; 
73     }
74     //判断是否是罪犯 
75     for(int i=1;i<=n;i++)
76     {
77         add(word(pre[i],1),is_crm(i,1));
78         add(is_crm(i,0),word(pre[i],0));
79     }
80     for(int i=1;i<=(n+m)<<1;i++)
81         if(!dfn[i])
82             tarjan(i);
83     for(int i=1;i<=n;i++)
84     {
85         if(co[i]==co[i+n])
86         {
87             printf("Impossible");
88             return 0;
89         }
90         else if(co[i]>co[i+n]) ans[++cnt]=i;
91     }
92     printf("%d\n",cnt);
93     for(int i=1;i<=cnt;i++) printf("%d ",ans[i]);
94     return 0;
95 }

 

转载于:https://www.cnblogs.com/Slrslr/p/9521852.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值