2_SAT
有N个变量,每个变量只有两种可能的取值。再给定M个条件,每个条件都是对1两个变量的取值限制。求是否存在N个变量的合法赋值,使M个条件均被满足。
我们可以把此类问题转换成统一的形式:若变量
A
i
A_i
Ai赋值为
A
i
p
A_ip
Aip,则变量
A
j
A_j
Aj必须赋值为
A
j
q
A_jq
Ajq,其中q,p为{0,1};
解:
首先,对于这张图中的每个强连通分量中的点一定要么同时选,要么同时不选。
那么,我们将图缩强连通分量。
这时候就可以判断无解了。如果xi,0和xi,1在同一个强连通分量中,那么明显无解。
此时,你就得到了一张拓扑图。这张拓扑图中,如果u可以到达v,那么u选择则v也必须选择。
那么,你可以利用强连通分量的标号来得到反向的拓扑序
for (int i = 1; i <= n; ++i)
print((color[i] < color[i + n])), putchar(' '); //注意大小号
puts("");
注意点:
对于建图方式,可以是 i 对于 i+n ,或者是 i 对应 i^1 (i+1)。
逆否命题不一定成对出现。
尤其注意特殊的连边方式,在必定的条件,即类似 a && b == 1,则a和b都必须为1,那么需要添加两条边a->a,b->b。(在此情况下,i必定是a,j必定是b)
逻辑上来说就是如果2发生则1必须会发生(由于1和2是对立事件,也就是说选择2的话会产生矛盾)。但是由于1并不能推出2,所以第一组仍然符合2-sat。这个时候如果规定在第一组中必须选择2。也就是加一条1–>2的边后就会使得1和2处于同一个强连通分量中,被判定无解。
模板:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+7,M = 2e6+7;
int n,m;
int cnt,head[N],ver[M],nex[M];
void add(int x,int y){
nex[++cnt] = head[x];
ver[cnt] = y;
head[x] = cnt;
}
int col,dfstime;
int dfn[N],low[N],id[N],all[N];//id是用来记录第i个点的颜色,也可以用作判断是否在栈中
stack<int> st;
void tarjan(int x){
dfn[x] = low[x] = ++dfstime;
st.push(x);
for(int i=head[x];i;i=nex[i]){
int y =ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
}else if(!id[y]) //判断是否在栈中
low[x] = min(low[x],dfn[y]);
}
int i;
if(low[x] == dfn[x]){
++col;
do{
i = st.top();st.pop();
id[i] = col;
}while(i!=x);
}
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1; i <= m;i++){
int x,a,y,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
add(x+(1-a)*n,y+b*n); //连边(非a,b)
add(y+(1-b)*n,x+a*n); //连边(非b,a)
}
}
int main(){
read();
for(int i=1;i <= 2*n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
if(id[i] == id[i+n]){
puts("IMPOSSIBLE");return 0;
}
puts("POSSIBLE");
for(int i=1;i<=n;i++)
printf("%d ",id[i] > id[i+n]);
return 0;
}
出现必定的情况:
poj 2296
#include <iostream>
#include <cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 2e4+7;
int n;
int x[N],y[N];
int col,dfstime;
int dfn[N],low[N],id[N],all[N];
int cnt,head[N],nex[N],ver[N];
stack<int> st;
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void init(){
for(int i = 0; i < N ;i++)
dfn[i] = low[i] = id[i] = all[i] = 0,head[i] = -1;
while(st.size()) st.pop();
col = dfstime = 0;
cnt = 0;
}
void read(){
for(int i = 0; i < N ;i ++)
x[i] = y[i] =0;
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%d%d",&x[i],&y[i]);
}
}
void pre(int d){
int maxs,mins;
for(int i=1; i <= n;i++){
for(int j = 1;j < i;j++){
if(abs(x[i] - x[j]) < d){
if(y[i] > y[j]) maxs = i,mins = j;
else maxs = j,mins = i;
if(y[maxs] == y[mins]){
add(maxs+n,mins),add(mins,maxs+n);
add(maxs,mins+n),add(mins+n,maxs);
}
else if(y[maxs] - y[mins] < 2*d){
if(y[maxs] - y[mins] < d)//此时一个必定向上,一个必定向下
add(maxs,maxs+n),add(mins+n,mins);//避免最后出现maxs和maxs+n在同一逻辑下但不在同一分量中的情况。
else{
add(maxs,mins),add(mins,maxs);
add(mins+n,maxs+n),add(mins+n,maxs+n);
}
}
}
}
}
}
void tarjan(int x){
dfn[x] = low[x] = ++ dfstime;
st.push(x);
for(int i = head[x] ; ~i; i=nex[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
}else if(!id[y])
low[x] = min(low[x],dfn[y]);
}
int i;
if(low[x] == dfn[x]){
++col;
do{
i = st.top();st.pop();
id[i] = col;
all[col] ++;
}while(i != x);
}
}
bool solve(int d){
init();
pre(d);
for(int i=1; i <= 2*n ;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i <= n;i++){
if(id[i] == id[i+n]){
return false;
}
}
return true;
}
int main(){
int T; scanf("%d",&T);
while(T--){
read();
int l = 0, r = 20010,ans=-1;
while(l <= r){
int mid = (l+r)>>1;
if(solve(mid)){
ans = mid,l = mid+1;
}else r = mid-1;
}
printf("%d\n",ans);
}
return 0;
}
不同的建图方式:
poj 3683
const int N = 4010,M=N*N;
int n;
int cnt=1,head[N],ver[M],nex[M];
int col,dfstime;
int s[N] ,t[N];
int dfn[N],low[N],id[N];//id是用来记录第i个点的颜色,也可以用作判断是否在栈中
stack<int> st;
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void tarjan(int x){
dfn[x] = low[x] = ++dfstime;
st.push(x);
for(int i=head[x]; ~i ; i=nex[i]){
int y =ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
}else if(!id[y]) //判断是否在栈中
low[x] = min(low[x],dfn[y]);
}
int i;
if(low[x] == dfn[x]){
++col;
do{
i = st.top();st.pop();
id[i] = col;
}while(i!=x);
}
}
int solve()
{
while(st.size()) st.pop();
col=dfstime = 0;
for (int i=0; i<2*n; i++)
{
if (!dfn[i])
tarjan(i);
}
for (int i=0;i<n;i++)//不在一个联通块里
{
if (id[i]==id[i^1]) return 0;
}
return 1;
}
bool against(int x,int y)
{
if((t[x]<=s[y])||(s[x]>=t[y])) return 0;
else return 1;
}
int main()
{
while (scanf ("%d",&n)!=EOF)
{
for(int i=0;i<N;i++) head[i] = -1,dfn[i]=low[i]=id[i]=0;
cnt = 1;
for (int i=0;i<n*2;i+=2)
{
int h1,m1,h2,m2,tmp;
scanf ("%d:%d%d:%d%d",&h1,&m1,&h2,&m2,&tmp);
s[i]=h1*60+m1;
t[i]=h1*60+m1+tmp;
s[i^1]=h2*60+m2-tmp;
t[i^1]=h2*60+m2;
}
memset(head,-1,sizeof(head));
for (int i=0;i<2*n;i++) //
{
for (int j=i+1;j<2*n;j++) //
{
if (i!=(j^1)&&against(i,j)) //
{
add(i,j^1); //
add(j,i^1); //
}
}
}
if (!solve()) printf ("NO\n");
else
{
printf ("YES\n");
for (int i=0;i<2*n;i+=2)
{
if (id[i]<id[i^1]) //
{
printf ("%02d:%02d %02d:%02d\n",s[i]/60,s[i]%60,t[i]/60,t[i]%60);
}
else
printf ("%02d:%02d %02d:%02d\n",s[i^1]/60,s[i^1]%60,t[i^1]/60,t[i^1]%60); //
}
}
}
return 0;
}