2-SAT
2-SAT 算法用于解决给定 n 个元素可0可1,及m个限制:“x为1/0或者y为1/0”。SAT=satisfication简称,2表示“”内的只有两个选择。目前证明(>2)-SAT是NP问题,但2-SAT有O(m)算法
考虑建图,每个点有x,x',表示x=1,0。考虑限制->连边,以“i为0或j为1”为例:这句话的意思是,如果i=1那j必=1,如果j=0则i必=0,考虑图中u->v表示选了u就必选v,那么连两条边——i->j,i'->j'
对图进行缩点,理由是同一scc内的点要么都选要么都不选。首先排除一种情况,就是\(\exists\)x和x'在同一强联通分量内,一个值不可能既真又假所以——无解。事实证明剩余情况都有解。
相当于我们已经知道有解,那我们考虑缩点后新图的拓扑序中如果x,x'不在一个连通块里那选哪个无所谓,否则若rk[x]<rk[x'],那么如果选x就肯定选到x'了,因此选x',否则选x。luogu2-sat
2-sat
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int stk[N],instk[N],bel[N],dfn[N],low[N],in[N],rk[N],Bcnt,t,tt,tot,n,m;
queue<int>Q;
vector<int>G[N],nG[N],B;
void scc(int x){
dfn[x]=low[x]=++tot;instk[x]=1;stk[++t]=x;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(!dfn[y]){
scc(y);
low[x]=min(low[x],low[y]);
}else if(instk[y])low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x]){
Bcnt++;B.clear();
while(t){
instk[stk[t]]=0;
bel[stk[t]]=Bcnt;
B.push_back(stk[t]);
if(stk[t--]==x)break;
}
for(int i=0;i<B.size();i++){
int xx=B[i];
for(int j=0;j<G[xx].size();j++){
int yy=G[xx][j];
if(bel[xx]!=bel[yy])nG[bel[xx]].push_back(bel[yy]),in[bel[yy]]++/*,printf("%d %d\n",bel[xx],bel[yy])*/;
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,a,b;scanf("%d%d%d%d",&x,&a,&y,&b);
if(a&&b)G[x+n].push_back(y),G[y+n].push_back(x);
if(a&&!b)G[x+n].push_back(y+n),G[y].push_back(x);
if(!a&&b)G[x].push_back(y),G[y+n].push_back(x+n);
if(!a&&!b)G[x].push_back(y+n),G[y].push_back(x+n);
}
for(int i=1;i<=n*2;i++)if(!dfn[i])scc(i);
int er=0;
for(int i=1;i<=n;i++)if(bel[i]==bel[i+n]){er=1;break;}
if(er){puts("IMPOSSIBLE");return 0;}puts("POSSIBLE");
for(int i=1;i<=Bcnt;i++)if(!in[i])Q.push(i);
while(!Q.empty()){
int x=Q.front();Q.pop();rk[x]=++tt;//cout<<x<<endl;
for(int i=0;i<nG[x].size();i++){
int y=nG[x][i];in[y]--;
if(!in[y])Q.push(y);
}
}
//for(int i=1;i<=n*2;i++)printf("%d ",bel[i]);puts("");
for(int i=1;i<=n;i++)printf("%d ",rk[bel[i]]<rk[bel[i+n]]?0:1);
}
注:其实可以不用topo,因为求强连通分量其实隐含着如果缩点后图x<y那么x的拓扑序应该大于y。