tarjan的运用
this is a problem:link
2-SAT处理的是什么
首先,把「2」和「SAT」拆开。SAT 是 Satisfiability 的缩写,意为可满足性。即一串布尔变量,每个变量只能为真或假。要求对这些变量进行赋值,满足布尔方程。
所以看这道题
若ai为真或aj为真,所以当ai为真时aj必须为假,若aj为真时ai必须为假
所以假设i为ai为真,i+n为ai为假
所以建边(i,j+n),(j,i+n),连接u,v表示选u就要选v
然后用可能出现环,所以用tarjan缩点表示若i,j在同一强联通分量中,那么他们两个必须要选
然后可以想无解情况,若i于i+n都在同一强连通分量中,于是无解,因为他们两个都必须要选,但却不能都选
然后想怎么输出解
因为拓扑序的关系 拓扑序在前的点有可能能够到达拓扑序靠后的点 而靠后的一定无法到达靠前的 因此选择靠后的一定能够保证出合法解 而选择靠前的可能会导致前面的点存在一条通路到达后面的点致使答案出现错误 这样可以感性理解,至于证明利用了建图过程中的对称性。什么意思呢1,就是tarjan为反拓扑顺序,所以说就要选择强连通编号中最大的
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 inline int read() 7 { 8 int f=1,ans=0;char c; 9 while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} 10 while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} 11 return f*ans; 12 } 13 struct node{ 14 int u,v,nex; 15 }x[2000001]; 16 int n,m,head[2000001],cnt,dfn[2000001],st[2000001],num,top,col,co[2000001],low[2000001],tot; 17 void tarjan(int xx) 18 { 19 low[xx]=dfn[xx]=++num; 20 st[++tot]=xx; 21 for(int i=head[xx];i!=-1;i=x[i].nex) 22 { 23 int v=x[i].v; 24 if(!dfn[v]) 25 { 26 tarjan(v); 27 low[xx]=min(low[xx],low[v]); 28 } 29 else if(!co[v]) low[xx]=min(low[xx],dfn[v]); 30 } 31 if(dfn[xx]==low[xx]) 32 { 33 co[xx]=++col; 34 while(st[tot]!=xx) 35 { 36 co[st[tot]]=col; 37 tot--; 38 } 39 tot--; 40 } 41 return; 42 } 43 void add(int u,int v) 44 { 45 x[cnt].u=u,x[cnt].v=v,x[cnt].nex=head[u],head[u]=cnt++; 46 } 47 int main() 48 { 49 memset(head,-1,sizeof(head)); 50 n=read(),m=read(); 51 for(int i=1;i<=m;i++) 52 { 53 int a=read(),xa=read(),b=read(),yb=read(); 54 if(xa==1&&yb==1) add(a,b+n),add(b,a+n); 55 if(xa==1&&yb==0) add(a,b),add(b+n,a+n); 56 if(xa==0&&yb==0) add(a+n,b),add(b+n,a); 57 if(xa==0&&yb==1) add(a+n,b+n),add(b,a); 58 } 59 for(int i=1;i<=2*n;i++) 60 if(!dfn[i]) tarjan(i); 61 for(int i=1;i<=n;i++) 62 if(co[i]==co[i+n]){cout<<"IMPOSSIBLE";return 0;} 63 cout<<"POSSIBLE"<<endl; 64 for(int i=1;i<=n;i++) 65 { 66 if(co[i]>co[i+n]) cout<<1<<" "; 67 else cout<<0<<" "; 68 } 69 return 0; 70 } 71 /* 72 3 2 73 1 1 3 0 74 3 1 1 0 75 */
一道2-SAT的例题
和平委员会 |
link |
试题描述
|
根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。
此委员会必须满足下列条件: 每个党派都在委员会中恰有 1 个代表, 如果 2 个代表彼此厌恶,则他们不能都属于委员会。 每个党在议会中有 2 个代表。代表从 1 编号到 2n。 编号为 2i−1 和 2i 的代表属于第 i 个党派。 任务:写一程序读入党派的数量和关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。 |
输入
|
第一行有两个非负整数 n 和 m。他们各自表示:党派的数量 n 和不友好的代表对 m。 接下来 m 行,每行为一对整数 a,b,表示代表 a,b 互相厌恶。
|
输出
|
如果不能创立委员会,则输出信息NIE。若能够成立,则输出包括 n 个从区间 1 到 2n 选出的整数,按升序写出,每行一个,这些数字为委员会中代表的编号。
如果委员会能以多种方法形成,程序可以只输出它们的某一个。 |
输入示例
|
3 2
1 3 2 4 |
输出示例
|
1
4 5 |
其他说明
|
数据范围与提示
1≤n≤8000,0≤m≤20000,1≤a<b≤2n |
一个2-SAT的板子,就是与上一道题的区别是,上一道题必须二选一,这一次可以两个都不选,所以选择拓扑序小的(想一想,为什么)、
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; inline int read() { int f=1,ans=0;char c; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } struct node{ int u,v,nex; }x[400001]; int n,m,cnt,head[200001],dfn[200001],low[200001],tot,num,st[200001],co[200001],col; void add(int u,int v) { x[cnt].u=u,x[cnt].v=v,x[cnt].nex=head[u],head[u]=cnt++; } int cx(int s) { if(s%2==0) return s-1; else return s+1; } void tarjan(int xx) { low[xx]=dfn[xx]=++num; st[++tot]=xx; for(int i=head[xx];i!=-1;i=x[i].nex) { int v=x[i].v; if(!dfn[v]) { tarjan(v); low[xx]=min(low[xx],low[v]); } else if(!co[v]) low[xx]=min(low[xx],dfn[v]); } if(dfn[xx]==low[xx]) { co[xx]=++col; while(st[tot]!=xx) { co[st[tot]]=col; tot--; } tot--; } return; } int main() { memset(head,-1,sizeof(head)); n=read(),m=read(); for(int i=1;i<=m;i++) { int u=read(),v=read(); add(u,cx(v)),add(v,cx(u)); } for(int i=1;i<=2*n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=2*n;i=i+2) { if(co[i]==co[i+1]) { cout<<"NIE"; return 0; } } for(int i=1;i<=2*n;i=i+2) { if(co[i]>co[i+1]) cout<<i+1<<endl; else cout<<i<<endl; } return 0; }