[2-sat专练]poj 3683,hdu 1814,hdu 1824,hdu 3622,hdu 4115,hdu 4421
文章目錄
- Priest John's Busiest Day
- code
- Peaceful Commission
- code
- Let's go home
- code
- Bomb Game
- code
- Eliminate the Conflict
- code
- Bit Magic
- code
Priest John’s Busiest Day
題目
司儀必須在婚禮開始或結束時出現,考慮把第iii場婚禮拆成兩個點
iii:表示司儀在婚禮iii開始時出現
i+ni+ni+n:表示司儀在iii婚禮結束時出現
然后直接O(n2)O(n^2)O(n2)建邊👇
①
如果i,ji,ji,j的婚禮開始時間段有交叉
那么iii選開始,jjj就只能選結束
②
如果iii開始時間段和jjj結束時間段有交叉
那么iii選開始,jjj就只能選開始
③
如果iii結束時間段和jjj結束時間段有交叉
那么iii選結束,jjj就只能選開始
④
如果iii結束和jjj開始有交叉
那么iii選結束,jjj就只能選結束
因為蒟蒻的雙重循環寫法問題,i,ji,ji,j和j,ij,ij,i都會遍歷到,所以i,ji,ji,j就只考慮單向連邊,反邊會在j,ij,ij,i時判斷
code
#include <stack> #include <queue> #include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define MAXN 2005 struct node {int Begin, End;node() {}node( int B, int E ) { Begin = B, End = E; } }p[MAXN]; queue < int > q; stack < int > st; vector < int > G[MAXN], Edge[MAXN]; int n, cnt, tot; int c[MAXN], du[MAXN], low[MAXN], dfn[MAXN], scc[MAXN], mbe[MAXN]; bool vis[MAXN];void Tarjan( int u ) {dfn[u] = low[u] = ++ cnt;st.push( 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( low[u] == dfn[u] ) {int v; tot ++;do {v = st.top(); st.pop();scc[v] = tot;vis[v] = 0;} while( v != u );} }bool check( int i, int j ) {if( p[i].Begin >= p[j].End || p[i].End <= p[j].Begin )return 0;elsereturn 1; }int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {int a, b, c, d, len;scanf( "%d:%d %d:%d %d", &a, &b, &c, &d, &len );p[i] = node( a * 60 + b, a * 60 + b + len );p[i + n] = node( c * 60 + d - len, c * 60 + d );}for( int i = 1;i <= n;i ++ )for( int j = 1;j <= n;j ++ ) {if( i == j ) continue;if( check( i, j ) ) G[i].push_back( j + n );if( check( i, j + n ) ) G[i].push_back( j );if( check( i + n, j ) ) G[i + n].push_back( j + n );if( check( i + n, j + n ) ) G[i + n].push_back( j );}for( int i = 1;i <= ( n << 1 );i ++ )if( ! dfn[i] ) Tarjan( i );for( int i = 1;i <= n;i ++ )if( scc[i] == scc[i + n] ) return ! printf( "NO\n" );else mbe[scc[i]] = scc[i + n], mbe[scc[i + n]] = scc[i];printf( "YES\n" );memset( c, -1, sizeof( c ) );for( int i = 1;i <= ( n << 1 );i ++ )for( int j = 0;j < G[i].size();j ++ )if( scc[i] == scc[G[i][j]] ) continue;else Edge[scc[G[i][j]]].push_back( scc[i] ), du[scc[i]] ++;for( int i = 1;i <= tot;i ++ )if( ! du[i] ) q.push( i );while( ! q.empty() ) {int u = q.front(); q.pop();if( c[u] == -1 ) c[u] = 1, c[mbe[u]] = 0;for( int i = 0;i < Edge[u].size();i ++ ) {int v = Edge[u][i];du[v] --;if( ! du[v] ) q.push( v );}}for( int i = 1;i <= n;i ++ )if( c[scc[i]] )printf( "%.2d:%.2d %.2d:%.2d\n", p[i].Begin / 60, p[i].Begin % 60, p[i].End / 60, p[i].End % 60 );elseprintf( "%.2d:%.2d %.2d:%.2d\n", p[i + n].Begin / 60, p[i + n].Begin % 60, p[i + n].End / 60, p[i + n].End % 60 );return 0; }Peaceful Commission
題目
2i,2i?12i,2i-12i,2i?1要分開,那么直接彼此建邊即可
要求字典序最小,其實發現我們是按順序dfsdfsdfs找解的,是能保證正確性的
code
#include <cstdio> #include <vector> #include <cstring> using namespace std; #define MAXN 16005 vector < int > G[MAXN]; int n, m, Top; int st[MAXN]; bool flag[MAXN];bool dfs( int u ) {if( flag[u ^ 1] ) return 0;if( flag[u] ) return 1;flag[u] = 1;st[++ Top] = u;for( int i = 0;i < G[u].size();i ++ )if( ! dfs( G[u][i] ) ) return 0;return 1; }bool two_sat() {for( int i = 0;i < ( n << 1 );i += 2 )if( ! flag[i] && ! flag[i ^ 1] ) {Top = 0;if( ! dfs( i ) ) {while( Top ) flag[st[Top]] = 0, Top --;if( ! dfs( i ^ 1 ) ) return 0;}}return 1; }int main() {while( ~ scanf( "%d %d", &n, &m ) ) {memset( flag, 0, sizeof( flag ) );for( int i = 0;i < ( n << 1 );i ++ )G[i].clear();for( int i = 1, a, b;i <= m;i ++ ) {scanf( "%d %d", &a, &b );a --, b --;G[a].push_back( b ^ 1 );G[b].push_back( a ^ 1 );}if( two_sat() ) {for( int i = 0;i < ( n << 1 );i += 2 )if( flag[i] ) printf( "%d\n", i + 1 );else printf( "%d\n", ( i ^ 1 ) + 1 );}elseprintf( "NIE\n" );}return 0; }Let’s go home
題目
注意區分‘對’,‘隊’的意思哦~
iii:表示iii選手留下
i+ni+ni+n:表示iii離開
①隊長iii和兩名隊員j,kj,kj,k
Ⅰ:如果隊長離開,那么兩位隊員必須全部留下
建邊i+n,ji+n,ji+n,j和i+n,ki+n,ki+n,k
Ⅱ:如果有一名隊員離開,那么隊長必須留下
注意同隊的兩名隊員之間是不會相互影響的,換言之,一名隊長和一名隊員留下也是符合條件的
因此不能胡亂建邊(j,k),(j+n,k+n)(j,k),(j+n,k+n)(j,k),(j+n,k+n)
建邊j+n,ij+n,ij+n,i和k+n,ik+n,ik+n,i
②一對隊員
注意兩名隊員同時離開也是符合題意的,所以不能胡亂建邊(i+n,j),(j+n,i)(i+n,j),(j+n,i)(i+n,j),(j+n,i)
Ⅰ:如果iii留下,jjj必須離開,建邊i,j+ni,j+ni,j+n
Ⅱ:如果jjj留下,iii必須離開,建邊j,i+nj,i+nj,i+n
code
#include <stack> #include <cstdio> #include <vector> #include <cstring> using namespace std; #define MAXN 30000 stack < int > st; vector < int > G[MAXN]; int n, T, M, cnt, tot; int dfn[MAXN], low[MAXN], scc[MAXN];void tarjan( int u ) {dfn[u] = low[u] = ++ cnt;st.push( u );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( ! scc[v] )low[u] = min( low[u], dfn[v] );}if( dfn[u] == low[u] ) {int v; ++ tot;do {v = st.top(); st.pop();scc[v] = tot;} while( u != v );} }int main() {while( ~ scanf( "%d %d", &T, &M ) ) {n = T * 3;cnt = tot = 0;memset( dfn, 0, sizeof( dfn ) );memset( low, 0, sizeof( low ) );memset( scc, 0, sizeof( scc ) );for( int i = 1;i <= ( n << 1 );i ++ )G[i].clear();while( ! st.empty() ) st.pop();for( int i = 1, a, b, c;i <= T;i ++ ) {scanf( "%d %d %d", &a, &b, &c );G[a + n].push_back( b );G[a + n].push_back( c );G[b + n].push_back( a );G[c + n].push_back( a );}for( int i = 1, a, b;i <= M;i ++ ) {scanf( "%d %d", &a, &b );G[a].push_back( b + n );G[b].push_back( a + n );}for( int i = 0;i < ( n << 1 );i ++ )if( ! dfn[i] ) tarjan( i );bool flag = 0;for( int i = 0;i < n;i ++ )if( scc[i] == scc[i + n] ) { flag = 1; break; }if( flag ) printf( "no\n" );else printf( "yes\n" );}return 0; }Bomb Game
題目
二分+2-sat
先把圓能移動的兩個邊界拆分成兩個點,iii,i+ni+ni+n
與上一題做法類似
考慮二分半徑,要求不同圓之間不會有交集
也就是半徑不會涉及,即兩個之間距離要超過r?2r*2r?2
在每次二分的半徑midmidmid中判斷是否有交叉
與上一道題建邊思想類似,不再贅述
其實就是如果a,ba,ba,b矛盾,那就建邊(a,b+n),(b,a+n)(a,b+n),(b,a+n)(a,b+n),(b,a+n)
把矛盾的兩個部分錯開
code
#include <cmath> #include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define eps 1e-5 #define MAXN 300 struct node {int x, y; }p[MAXN]; vector < int > G[MAXN]; int cnt, tot, n, Top; int dfn[MAXN], low[MAXN], scc[MAXN], st[MAXN];void tarjan( int u ) {dfn[u] = low[u] = ++ cnt;st[++ Top] = u;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( ! scc[v] )low[u] = min( low[u], dfn[v] );}if( dfn[u] == low[u] ) {int v; ++ tot;do {v = st[Top --];scc[v] = tot;} while( u != v && Top );} }bool check() {for( int i = 1;i <= n;i ++ )if( scc[i] == scc[i + n] ) return 0;return 1; }double dis( int x1, int y1, int x2, int y2 ) {return sqrt( 1.0 * ( x1 - x2 ) * ( x1 - x2 ) + 1.0 * ( y1 - y2 ) * ( y1 - y2 ) ); }int main() {while( ~ scanf( "%d", &n ) ) {for( int i = 1;i <= n;i ++ )scanf( "%d %d %d %d", &p[i].x, &p[i].y, &p[i + n].x, &p[i + n].y );double l = 0, r = 10000, mid;while( r - l > eps ) {cnt = tot = Top = 0;mid = ( l + r ) / 2;for( int i = 1;i <= ( n << 1 );i ++ )G[i].clear();memset( dfn, 0, sizeof( dfn ) );memset( low, 0, sizeof( low ) );memset( scc, 0, sizeof( scc ) );for( int i = 1;i < n;i ++ )for( int j = i + 1;j <= n;j ++ ) {if( dis( p[i].x, p[i].y, p[j].x, p[j].y ) < mid * 2 ) {G[i].push_back( j + n );G[j].push_back( i + n );}if( dis( p[i].x, p[i].y, p[j + n].x, p[j + n].y ) < mid * 2 ) {G[i].push_back( j );G[j + n].push_back( i + n );}if( dis( p[i + n].x, p[i + n].y, p[j].x, p[j].y ) < mid * 2 ) {G[i + n].push_back( j + n );G[j].push_back( i );}if( dis( p[i + n].x, p[i + n].y, p[j + n].x, p[j + n].y ) < mid * 2 ) {G[i + n].push_back( j );G[j + n].push_back( i );}}for( int i = 1;i <= n;i ++ )if( ! dfn[i] ) tarjan( i );if( check() ) l = mid;else r = mid;}printf( "%.2lf\n", mid );}return 0; }Eliminate the Conflict
題目
如果沒有bobbobbob的出拳方式,單單只給i,ji,ji,j輪的限制,這道題就會更加水
其實就算加上bobbobbob的限制也很簡單…
對于bobbobbob的出拳,AliceAliceAlice要想不輸就必須打平或者贏
提前處理出AAA在iii輪的兩種出拳方式,然后根據后面給的限制,建邊即可
code
#include <cstdio> #include <vector> #include <cstring> using namespace std; #define MAXN 20005 vector < int > G[MAXN]; int T, n, m, cnt, tot, Top; int dfn[MAXN], low[MAXN], scc[MAXN], st[MAXN]; int bob[MAXN][2];void tarjan( int u ) {dfn[u] = low[u] = ++ cnt;st[++ Top] = u;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( ! scc[v] )low[u] = min( low[u], dfn[v] );}if( dfn[u] == low[u] ) {int v; ++ tot;do {v = st[Top --];scc[v] = tot;} while( u != v && Top );} }int main() {scanf( "%d", &T );for( int Case = 1;Case <= T;Case ++ ) {memset( dfn, 0, sizeof( dfn ) );memset( low, 0, sizeof( low ) );memset( scc, 0, sizeof( scc ) );cnt = tot = Top = 0;for( int i = 1;i <= ( n << 1 );i ++ )G[i].clear();scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ ) {scanf( "%d", &bob[i][0] );bob[i][0] --;bob[i][1] = ( bob[i][0] + 1 ) % 3;}for( int i = 1, a, b, k;i <= m;i ++ ) {scanf( "%d %d %d", &a, &b, &k );if( ! k ) {if( bob[a][0] != bob[b][0] ) {G[a].push_back( b + n );G[b].push_back( a + n );}if( bob[a][0] != bob[b][1] ) {G[a].push_back( b );G[b + n].push_back( a + n );}if( bob[a][1] != bob[b][0] ) {G[a + n].push_back( b + n );G[b].push_back( a );}if( bob[a][1] != bob[b][1] ) {G[a + n].push_back( b );G[b + n].push_back( a );}}else {if( bob[a][0] == bob[b][0] ) {G[a].push_back( b + n );G[b].push_back( a + n );}if( bob[a][0] == bob[b][1] ) {G[a].push_back( b );G[b + n].push_back( a + n );}if( bob[a][1] == bob[b][0] ) {G[a + n].push_back( b + n );G[b].push_back( a );}if( bob[a][1] == bob[b][1] ) {G[a + n].push_back( b );G[b + n].push_back( a );}}}for( int i = 1;i <= ( n << 1 );i ++ )if( ! dfn[i] ) tarjan( i );bool flag = 1;for( int i = 1;i <= n;i ++ )if( scc[i] == scc[i + n] ) { flag = 0; break; }if( flag ) printf( "Case #%d: yes\n", Case );else printf( "Case #%d: no\n", Case );}return 0; }Bit Magic
題目
iii:表示a[i]=1a[i]=1a[i]=1
i+ni+ni+n:表示a[i]=0a[i]=0a[i]=0
拆分成二進制,每一位來一次2-sat,有一位矛盾就NONONO
對于第kkk位的數,b[i][j]b[i][j]b[i][j]二進制上第kkk位有兩種情況👇
①b[i][j]&(1<<k)==1b[i][j]\&(1<<k)==1b[i][j]&(1<<k)==1,kkk位是111
Ⅰ:i%2==1&&j%2==1i\%2==1\&\&j\%2==1i%2==1&&j%2==1
那么只要i,ji,ji,j中有一個對應的aaa值第kkk位上為111
也可以同時為1
所以如果a[i]a[i]a[i]第kkk位不是111,a[j]a[j]a[j]就必須111
建邊(i+n,j),(j+n,i)(i+n,j),(j+n,i)(i+n,j),(j+n,i)
Ⅱ:i%2==0&&j%2==0i\%2==0\&\&j\%2==0i%2==0&&j%2==0
i,ji,ji,j必須同時為111,建邊(i,j),(j,i)(i,j),(j,i)(i,j),(j,i)
Ⅲ:剩下的情況,一奇一偶
這個時候要調用異或的法則了,必須一個為111,另一個000才能異或出來111
建邊(i,j+n),(j,i+n),(i+n,j),(j+n,i)(i,j+n),(j,i+n),(i+n,j),(j+n,i)(i,j+n),(j,i+n),(i+n,j),(j+n,i)
②b[i][j]&(1<<k)==0b[i][j]\&(1<<k)==0b[i][j]&(1<<k)==0,kkk位是000
Ⅰ:i%2==1&&j%2==1i\%2==1\&\&j\%2==1i%2==1&&j%2==1
為了滿足kkk位或出來的結果為000,必須i,ji,ji,j都為000
建邊(i+n,j+n),(j+n,i+n)(i+n, j+n),(j+n,i+n)(i+n,j+n),(j+n,i+n)
Ⅱ:i%2==0&&j%2==0i\%2==0\&\&j\%2==0i%2==0&&j%2==0
i,ji,ji,j只要有一個不為111
也可以同時不為1,所以當iii為0的時候,是不確定jjj的取值,沒有矛盾地方,無法進行建邊
建邊(i,j+n),(j,i+n)(i,j+n),(j,i+n)(i,j+n),(j,i+n)
Ⅲ:剩下的情況,一奇一偶
要么同時為111,同時000
建邊(i,j),(j,i),(i+n,j+n),(j+n,i+n)(i,j),(j,i),(i+n,j+n),(j+n,i+n)(i,j),(j,i),(i+n,j+n),(j+n,i+n)
code
#include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define MAXN 1005 vector < int > G[MAXN]; int n, Top, cnt, tot; int dfn[MAXN], low[MAXN], scc[MAXN], st[MAXN]; int b[MAXN][MAXN];void tarjan( int u ) {dfn[u] = low[u] = ++ cnt;st[++ Top] = u;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( ! scc[v] )low[u] = min( low[u], dfn[v] );}if( dfn[u] == low[u] ) {int v; tot ++;do {v = st[Top --];scc[v] = tot;} while( u != v && Top );} }void init() {cnt = tot = Top = 0;memset( scc, 0, sizeof( scc ) );memset( dfn, 0, sizeof( dfn ) );for( int i = 0;i < ( n << 1 );i ++ )G[i].clear(); }int main() {while( ~ scanf( "%d", &n ) ) {for( int i = 0;i < n;i ++ )for( int j = 0;j < n;j ++ )scanf( "%d", &b[i][j] );bool flag = 0;for( int i = 0;i < n;i ++ ) {for( int j = 0;j < n;j ++ )if( i == j && b[i][j] ) { flag = 1; break; }else if( b[i][j] != b[j][i] ) { flag = 1; break; }if( flag ) break;}if( flag ) { printf( "NO\n" ); break; }for( int k = 0;k < 31;k ++ ) {init();for( int i = 0;i < n;i ++ )for( int j = i + 1;j < n;j ++ ) {int temp = ( b[i][j] & ( 1 << k ) );if( temp ) {if( ( i & 1 ) && ( j & 1 ) ) {G[i + n].push_back( j );G[j + n].push_back( i );}else if( i % 2 == 0 && j % 2 == 0 ) {G[i].push_back( j );G[j].push_back( i );}else {G[i].push_back( j + n );G[i + n].push_back( j );G[j].push_back( i + n );G[j + n].push_back( i );}}else {if( ( i & 1 ) && ( j & 1 ) ) {G[i + n].push_back( j + n );G[j + n].push_back( i + n );}else if( i % 2 == 0 && j % 2 == 0 ) {G[i].push_back( j + n );G[j].push_back( i + n );}else {G[i].push_back( j );G[j].push_back( i );G[i + n].push_back( j + n );G[j + n].push_back( i + n );}}}for( int i = 0;i < ( n << 1 );i ++ )if( ! dfn[i] ) tarjan( i );for( int i = 0;i < n;i ++ )if( scc[i] == scc[i + n] ) {flag = 1;break;}if( flag ) break;}if( flag ) printf( "NO\n" );else printf( "YES\n" );}return 0; }感覺這幾道模板都挺水的…怎么回事??
2-sat就是會建邊就會A,凡事建完后直接tarjan
總結
以上是生活随笔為你收集整理的[2-sat专练]poj 3683,hdu 1814,hdu 1824,hdu 3622,hdu 4115,hdu 4421的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支持双卡双待:酷比魔方掌玩 mini 平
- 下一篇: VeriFace是什么?