0.定义
先定义(k-SAT)问题:
给出(n)个数和(m)个形如(x_1oplus x_2opluscdotsoplus x_k=0/1)的关系式((oplus)为(land ,lor)等运算符),询问是否有解,如果有解,求出(x_1,x_2,cdots ,x_k)的值。
(k>2)的已经证明是(NPC)了,我们只考虑(2-SAT)问题
1.解法
我们已经知道问题是什么样了,接下来该考虑怎么求了
题中给的关系都不确定,我们考虑如何转成确定的关系
遇到这类问题我们常常考虑拆点,那么不妨也在这题试试:
把每个点(i)拆成(i,lnot i),然后考虑连边
这里我们举一个很好理解的例子:(iland j=1),求(i,j)
第一种情况:(i=0)
此时(j)只能等于1,于是从(lnot i)连到(j)
第二种情况:(j=0)
此时(i)只能等于1,于是从(lnot j)连到(i)
剩下的情况可以自己推一下,代码中用了一个比较方便的位运算简化版
然后呢?
首先判断是否有解
可以很显然地想到:存在点(i)和(lnot i)在一个强连通分量里(Leftrightarrow)无解
这句话的意思就是如果有一个点为1,但是经过一些推导之后得到该点为0,那么就无解
那么如果有解的话我们怎么求出这个解呢?
直接亮出正解:选择(i,lnot i)中拓扑序较大的点
因为(Tarjan)是回溯的时候记录,所以要输出编号小的
2.代码
省略了缺省源
#define N 1000000
#define M 2000010
int n,m,dfn[M],low[M],bel[M],ins[M],tim,num;
struct Edge {
int nxt,to;
}e[M];
int head[M],cnt;
inline void ade(int u,int v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
stack<int>s;//以下为Tarjan板子
void Tarjan(int now){
dfn[now]=low[now]=++tim;
s.push(now),ins[now]=1;
for(rg int i=head[now];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v);
low[now]=min(low[now],low[v]);
}else if(ins[v]){
low[now]=min(low[now],dfn[v]);
}
}
if(dfn[now]==low[now]){
num++;
do {
bel[now]=num;
now=s.top();
s.pop(),ins[now]=0;
}while(dfn[now]!=low[now]);
}
}
int main(){
Read(n),Read(m);
for(rg int i=1;i<=m;i++){
int a,b,ba,bb;
Read(a),Read(ba),Read(b),Read(bb);
ade(a+n*(ba&1),b+n*(bb^1));//小小的位运算简化代码
ade(b+n*(bb&1),a+n*(ba^1));
}
for(rg int i=1;i<=(n<<1);i++){
if(!dfn[i])Tarjan(i);
}
for(rg int i=1;i<=n;i++){
if(bel[i]==bel[i+n]){//判断无解
cout<<"IMPOSSIBLE"<<endl;
return 0;
}
}
cout<<"POSSIBLE"<<endl;
for(rg int i=1;i<=n;i++){//注意要用小于号
cout<<(bel[i]<bel[i+n])<<" ";
}
return 0;
}