练习一下2-SAT问题
【练习一】http://acm.hdu.edu.cn/showproblem.php?pid=3622
这题是2010天津网络赛试题。
二分答案,将圆看做一个点,当两个圆a,b的距离小于给定距离d时,说明a,b互斥,连边<a , ~b> ,<~b , a> 。
二分+ 2_SAT 。 这确实是个好思路。
/**//**//**//*
2010 Asia Tianjin Regional online Contest
Problem B: Bomb Game
Methord : binary search + 2-SAT
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std ;
const int MAXN = 205 ;
const int MAXE = MAXN*MAXN ;
const double EPS = 1e-3 ;
struct Edge{
int y , next;
};
struct Point{
double x , y ;
};
Edge e[MAXE] ;
Point P[MAXN] ;
int es , hd[MAXN] , n ;
double dist[MAXN][MAXN] ;
inline int sgn(double x){
return (x>EPS)-(x<-EPS);
}
inline void ins(int x ,int y){
e[es].y = y , e[es].next = hd[x] , hd[x] = es ++ ;
}
inline double cal(Point a, Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)) ;
}
int low[MAXN] ,dfn[MAXN],stk[MAXN] , instk[MAXN] , bel[MAXN] , bcnt , idx , top;
void Tarjan(int x ){
int i , y;
low[x] = dfn[x] = ++idx ;
stk[top++] = x ; instk[x] = 1 ;
for(i = hd[x] ; i!=-1 ; i=e[i].next){
y = e[i].y ;
if(dfn[y]==-1){
Tarjan(y) ;
low[x] = min(low[x] , low[y]) ;
}else if(instk[y]){
low[x] = min(low[x] , dfn[y]) ;
}
}
if(low[x] == dfn[x]){
++ bcnt ;
do{
y = stk[--top] ;
instk[y] = 0 ;
bel[y] = bcnt ;
}while(y!=x);
}
}
int check(double d){
int i , j , k ;
es = 0 ; memset(hd , -1 ,sizeof(hd));
for(i = 1 ; i<= n; ++i){
for(j = i+1 ; j<=n ; ++j){
if(dist[2*i-1][2*j-1]< d )
ins(2*j-1 , 2*i) , ins(2*i-1 , 2*j);
if(dist[2*j-1][2*i]< d)
ins(2*j-1 , 2*i-1) , ins(2*i ,2*j);
if(dist[2*j][2*i-1]< d)
ins(2*j , 2*i) , ins(2*i-1,2*j-1);
if(dist[2*j][2*i]< d)
ins(2*j , 2*i-1) , ins(2*i,2*j-1);
}
}
idx = bcnt = top = 0 ;
memset(dfn , -1 ,sizeof(dfn));
memset(instk , 0,sizeof(instk));
for(i = 1 ; i<=2*n ;++i)
if(dfn[i] == -1)
Tarjan(i);
for(i = 1 ; i<=n ; ++i)
if(bel[2*i-1] == bel[2*i]) return 0 ;
return 1;
}
int main(){
int i , j ;
double l , r ,mid ;
while(scanf("%d" , &n)!=EOF){
for(i = 1 ; i <=2*n ; ++i)
scanf("%lf%lf",&P[i].x ,&P[i].y);
for(i = 1 ; i <=2*n ; ++i)
for(j = 1 ; j<=2*n ; ++j)
dist[i][j] = cal(P[i] ,P[j]);
l = 0 , r = 1e6 ;
while(sgn(l-r)){
mid = (l + r) / 2.0 ;
if(check(mid))
l = mid ;
else
r = mid ;
}
printf("%0.2lf\n" , l/2.0);
}
return 0 ;
}
【练习二】
http://poj.org/problem?id=3207
只能遗憾的讲,这题考晦涩。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<memory>
#define MAXN 1005
using namespace std ;
int l[501] , r[501] , n , m;
bool gh[1001][1001];
bool IsCross(int x ,int y){
return (l[y]<=l[x] && l[x]<=r[y]) != (l[y]<=r[x] && r[x]<=r[y]) ;
}
int bcnt , top , idx , stk[MAXN] ,instk[MAXN] , dfn[MAXN] ,low[MAXN] , bel[MAXN];
void Tarjan(int x){
int y ;
dfn[x] = low[x] = ++ idx ;
instk[x] = 1 ; stk[top++] = x ;
for(y = 1 ; y<=2*m ; ++y)
if(gh[x][y]){
if(dfn[y] == -1){
Tarjan(y);
low[x] = min(low[x] , low[y]) ;
}else if(instk[y]){
low[x] = min(low[x] , dfn[y]) ;
}
}
if( dfn[x] == low[x]){
++ bcnt ;
do{
y = stk[--top] ;
instk[y] = 0 ;
bel[y] = bcnt ;
}while(y!=x);
}
}
int main(){
int i , j , k , ans;
scanf("%d%d" , &n , &m);
for(i = 1 ; i <= m ; ++i){
scanf("%d%d" ,&l[i] , &r[i]);
if(l[i] > r[i]) swap(l[i] , r[i]);
}
memset(gh , 0 , sizeof(gh));
for(i = 1 ; i <= m ; ++ i){
for( j = i + 1 ; j<= m ; ++j)
if(IsCross(i , j)){
gh[2*i-1][2*j] = gh[2*j-1][2*i] = 1 ;
gh[2*i][2*j-1] = gh[2*j][2*i-1] = 1 ;
}
}
bcnt = top = idx = 0 ;
memset(dfn , -1 ,sizeof(dfn));
memset(instk ,0 ,sizeof(instk));
for(i = 1 ; i <=2*m ; ++i)
if(dfn[i] == -1)
Tarjan(i);
ans = 1 ;
for(i = 1 ; i<=m ; ++i)
if(bel[2*i-1] == bel[2*i]){
ans = 0 ; break ;
}
if(ans)
printf("panda is telling the truth\n");
else
printf("the evil panda is lying again\n");
return 0 ;
}
【练习三】
http://poj.org/problem?id=3678
题目中的0,1让建模显得不是那么难了。
#include<iostream>
#include<cstring>
#define MAXN 2005
using namespace std ;
struct Edge{
int y , next ;
};
Edge e[MAXN*MAXN] ;
int es , hd[MAXN];
int Vcnt , Ecnt ;
inline void ins(int x ,int y){
e[es].y = y , e[es].next = hd[x] , hd[x] = es ++ ;
}
bool cal(bool x , bool y , char cmd[]){
if(!strcmp(cmd ,"AND")){
return x & y ;
}else if(!strcmp(cmd ,"OR")){
return x|y ;
}else if(!strcmp(cmd,"XOR")){
return x^y ;
}
}
int bcnt , top , idx ;
int stk[MAXN] ,instk[MAXN] ,dfn[MAXN] , low[MAXN] ,bel[MAXN];
void Tarjan(int x){
int i ,y ;
dfn[x] = low[x] = ++ idx ;
instk[x] = 1 , stk[top++] = x ;
for(i = hd[x] ; i!=-1 ;i=e[i].next){
y = e[i].y ;
if(dfn[y] == -1){
Tarjan(y) ;
low[x] = min(low[x] ,low[y]) ;
}else if(instk[y]){
low[x] = min(low[x] ,dfn[y]);
}
}
if( low[x] == dfn[x]){
++ bcnt ;
do{
y = stk[--top] ; instk[y] = 0 ; bel[y] = bcnt ;
}while(y!=x);
}
}
int main(){
int i , x , y , c;
char cmd[10] ;
scanf("%d%d" , &Vcnt ,&Ecnt);
es = 0 ; memset(hd , -1 ,sizeof(hd));
while(Ecnt--){
scanf("%d%d%d%s",&x,&y,&c,cmd); ++ x ; ++ y ;
if(cal(0,0,cmd) != c ){
ins(2*x-1,2*y) , ins(2*y-1,2*x) ;
}
if(cal(0,1,cmd) != c){
ins(2*x-1,2*y-1) ,ins(2*y,2*x) ;
}
if(cal(1,0,cmd) != c){
ins(2*x ,2*y) , ins(2*y-1,2*x-1) ;
}
if(cal(1,1,cmd) != c){
ins(2*x,2*y-1) , ins(2*y,2*x-1);
}
}
bcnt = idx = top = 0 ;
memset(dfn , -1 , sizeof(dfn)) ;
memset(instk , 0 , sizeof(instk));
for(i = 1 ; i<=Vcnt*2 ; ++i)
if(dfn[i] == -1)
Tarjan(i);
for(i = c = 1 ; i<=Vcnt ;++i)
if(bel[2*i-1] == bel[2*i] ){
c = 0 ; break ;
}
if(c)
puts("YES");
else
puts("NO");
return 0 ;
}
【练习四】
http://poj.org/problem?id=2723
想来想去,还是这个题目比较经典。
老规矩,二分 + 2_SAT判断。
有两种思维方式:
<1>. 2*N个钥匙用或不用 ;
<2>. 每扇门用锁a或者锁b ;
但是,仔细想来<1>的建图复杂度更低,而我却遗憾的用了<2>。
介绍两个版本:
版本一: http://blog.csdn.net/xiaotiandm/archive/2010/10/11/5933651.aspx
版本二:
#include<iostream>
#define MAXN ((1<<12)+3)
using namespace std;
int key , door , Pair[MAXN] , lock[MAXN] , N;
bool gh[MAXN][MAXN] , instk[MAXN];
int bcnt ,idx ,top , dfn[MAXN] , low[MAXN],stk[MAXN] , bel[MAXN];
void Tarjan(int x){
int i , y ;
low[x] = dfn[x] = ++ idx ;
instk[x] = 1 , stk[top ++ ] = x ;
for(y = 0 ; y < N ; ++ y)
if(gh[x][y]){
if(dfn[y] == -1){
Tarjan(y) ;
low[x] = min(low[x] , low[y]) ;
}else if(instk[y]){
low[x] = min(low[x] , dfn[y]) ;
}
}
if(low[x] == dfn[x]){
++ bcnt ;
do{
y = stk[--top] ; instk[y] = 0 ; bel[y] = bcnt;
}while(y!=x);
}
}
bool check(int MAX_DOOR){
int i , j , k ;
N = 2*MAX_DOOR ;
for(i = 0 ; i < N ; ++i)
for(j = 0 ; j < N ; ++j)
gh[i][j] = 0 ;
for(i = 0 ; i < MAX_DOOR ; ++ i)
for(j = i + 1; j < MAX_DOOR ; ++j){
if(Pair[lock[2*i]] == lock[2*j]) gh[2*i][2*j+1] = gh[2*j][2*i+1] = 1;
if(Pair[lock[2*i]] == lock[2*j+1]) gh[2*i][2*j] = gh[2*j+1][2*i+1] = 1;
if(Pair[lock[2*i+1]] == lock[2*j]) gh[2*i+1][2*j+1]= gh[2*j][2*i] = 1;
if(Pair[lock[2*i+1]] == lock[2*j+1]) gh[2*i+1][2*j] = gh[2*j+1][2*i] = 1;
}
bcnt = top = idx = 0 ;
for(i = 0 ; i < N ; ++ i) instk[i] = 0 , dfn[i] = -1 ;
for(i = 0 ; i < N ; ++ i)
if(dfn[i] == -1) Tarjan(i);
for(i = 0 ; i < MAX_DOOR ; ++ i)
if(bel[2*i] == bel[2*i+1]) return false;
return true;
}
int main(){
int i ,x , y , l , r , mid , ans;
while(scanf("%d%d", &key,&door)!=EOF && (key+door)){
for(i = 0 ; i<key ; ++i){
scanf("%d%d",&x,&y);
Pair[x] = y , Pair[y] = x ;
}
for(i = 0 ; i<2*door ;++i) scanf("%d" , &lock[i]);
ans = 1 , l = 1 , r = door ;
while(l <= r){
mid = (l + r) / 2 ;
if(check(mid)){
ans = mid ; l = mid + 1 ;
}else{
r = mid - 1;
}
}
printf("%d\n" , ans);
}
return 0 ;
}