[UOJ210]-寻找罪犯-前缀边优化2-SAT
2018-01-19 20:48
363 查看
说在前面
学了一天2-SAT从不会到寻找罪犯,感觉成就感满满
顺便还嘴巴AC了 NOI2017游戏 (虽然这就是一道水题,真的是水题)
题目
UOJ210传送门题面me就不贴了
要看的话可以戳传送门
解法
Emmmm网上的做法为什么都那么简单啊woc?感觉全世界就me建边最多这道题的限制条件还是很清晰了:
1. 一句话,不是真的就是假的
2. 一个人,不是真人(好人)就是假人(坏人)
3. 一个人如果是假人,那么至多有一句话是假的
然而发现这第3个条件十分坑,数据规模太大,直接建边是会爆炸的…
于是对于每个人,再定义一个前缀真话,表示这个人的 这句话以及这句话之前的所有话中,是否全是真话
建边很好想的,画个图梳理逻辑结构(图me会附在后面),然后建好了跑就是。
最后输出方案的时候,选择所在强连通分量编号较小的那个即可。关于正确性:如果A和!A在不同的图里面,那么选谁都是一样的。如果A和!A在同一张图里,就需要选拓扑序靠后的,而先dfs到的强连通分量,深度更深,拓扑序也相对靠后。
下面是me自带大常数的代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std ; int N , M , tp , id_c , head[600007] ; int personT[100005] , personF[100005] , provT[100005] , provF[100005] ; int preT[100005] , preF[100005] , las[100005] , oppo[600005] ; struct Path{ int pre , to ; }p[100000*10 + 100000*2 + 5]; void GG(){ puts( "Impossible" ) ; exit( 0 ) ; } void In( int t1 , int t2 ){ p[++tp].pre = head[t1] ; p[ head[t1] = tp ].to = t2 ; } int sta[600005] , topp ; int dfn[600005] , dfs_c , scc[600005] , scc_cnt ; int dfs( int u ){ sta[++topp] = u ; int lowu = dfn[u] = ++dfs_c ; for( int i = head[u] ; i ; i = p[i].pre ){ int v = p[i].to ; if( !dfn[v] ) lowu = min( lowu , dfs( v ) ) ; else if( !scc[v] ) lowu = min( lowu , dfn[v] ) ; } if( lowu == dfn[u] ){ ++scc_cnt ; while( 1 ){ int x = sta[topp--] ; scc[x] = scc_cnt ; if( x == u ) break ; } } return lowu ; } bool choose[600005] ; void solve(){ for( int i = 1 ; i <= id_c ; i ++ ) if( !dfn[i] ) dfs( i ) ; for( int i = 1 ; i <= id_c ; i ++ ){ if( scc[i] == scc[ oppo[i] ] ) GG() ; else if( scc[i] < scc[ oppo[i] ] ) choose[ scc[i] ] = true ; else choose[ scc[oppo[i]] ] = true ; } int cnt = 0 ; for( int i = 1 ; i <= N ; i ++ ) if( choose[ scc[personF[i]] ] ) cnt ++ ; printf( "%d\n" , cnt ) ; for( int i = 1 ; i <= N ; i ++ ) if( choose[ scc[personF[i]] ] ) printf( "%d " , i ) ; } void init(){ for( int i = 1 ; i <= N ; i ++ ){ personT[i] = ++id_c , personF[i] = ++id_c ; oppo[ personT[i] ] = personF[i] ; oppo[ personF[i] ] = personT[i] ; } for( int i = 1 ; i <= M ; i ++ ){ preT[i] = ++id_c , preF[i] = ++id_c ; provT[i] = ++id_c , provF[i] = ++id_c ; oppo[ preT[i] ] = preF[i] ; oppo[ preF[i] ] = preT[i] ; oppo[ provT[i]] = provF[i] ; oppo[ provF[i]] = provT[i] ; } } int main(){ scanf( "%d%d" , &N , &M ) ; init() ; for( int i = 1 , x , y , isgood ; i <= M ; i ++ ){ scanf( "%d%d%d" , &x , &y , &isgood ) ; if( isgood ){//证词和实际的人可互推 In( provT[i] , personT[y] ) ; In( personT[y] , provT[i] ) ; In( provF[i] , personF[y] ) ; In( personF[y] , provF[i] ) ; } else { In( provT[i] , personF[y] ) ; In( personF[y] , provT[i] ) ; In( provF[i] , personT[y] ) ; In( personT[y] , provF[i] ) ; } In( preT[i] , provT[i] ) ;//前缀和当前证词的关系 In( provF[i] , preF[i] ) ; if( las[x] ){ In( preT[i] , preT[ las[x] ] ) ;//前缀间关系 In( preF[ las[x] ] , preF[i] ) ; In( preF[ las[x] ] , provT[i] ) ;//前一个前缀与当前证词的关系 In( provF[i] , preT[ las[x] ] ) ; } las[x] = i ; } for( int i = 1 ; i <= N ; i ++ ) if( las[i] ){//前缀 和 实际的人的关系 In( personT[i] , preT[ las[i] ] ) ; In( preF[ las[i] ] , personF[i] ) ; } solve() ; }
附录1:逻辑关系图
箭头表示推导关系,直接按照这个关系建图即可
最后一个供词前缀 和 其他供词前缀 实际上是连起来的(画图太难用了,所以me没有画出来)
me感觉2-sat的题都可以通过画这样的图的关系来理一理思路,但是在画图的时候一定要考虑到所有情况!
相关文章推荐
- [UOJ]210 寻找罪犯 2-Sat 前缀和优化
- UOJ210【UER #6】寻找罪犯 (2-SAT前后缀优化建边)
- UOJ #210. 【UER #6】寻找罪犯 2-sat 前缀优化建边 详解
- 【UOJ #210】【UER #6】寻找罪犯 (2-SAT)
- uoj #210. 【UER #6】寻找罪犯 2-SAT
- 【UOJ #210】【UER #6】寻找罪犯 (2-sat 详解)
- UOJ 210 [UER #6]寻找罪犯
- UOJ#210. 【UER #6】寻找罪犯 2-sat
- [二分 前缀优化建图 2-SAT] Codeforces 587D. Duff in Mafia
- BZOJ.3495.[PA2010]Riddle(2-SAT 前缀优化建图)
- 【CF587D】Duff in Mafia 二分+前缀优化建图+2-SAT
- [线段树 & 前缀 优化建图 二分 2-SAT] CF Gym100159 facebook-hacker-cup-2012 I. Unfriending
- [BZOJ]3495 Riddle 2-Sat 前缀和优化
- [二分答案 2-SAT验证 前缀后缀优化建图 线段树优化建图] Codeforces gym 100159 Facebook Hacker Cup 2012 I. Unfriending
- bzoj3495 PA2010 Riddle(2-SAT 前缀优化建边)
- 【2-SAT+前缀优化建图】BZOJ3495 PA2010 Riddle
- Gym - 101102J J. Divisible Numbers 位运算+优化+前缀和
- 违禁词过滤完整设计与优化(前缀匹配、二分查找)
- 优化算法-寻找最优解
- 【BZOJ4945&&UOJ317】游戏(2-sat,拓扑序)