2- sat 問題
序
我笑笑,np完全,彈指一揮間罷了
正文
定義
2-SAT就是2判定性問題,是一種特殊的邏輯判定問題。
我們先來看看什么2-sat,問題,他大概可以理解為,給你一堆bool型變量,每個變量可能為真或假,現在有一種限制關系指
假如\(xi\)變量選了什么,\(yi\)只能是什么。我們稱變量只有兩種可能性的叫2-sat問題,而3-sat或更高的sat不行,因為他們是NP完全的。
我們通過建圖來操作2-sat問題
我們來看一個實際的題來說明
eg和平委員會
有n個組,第i個組里有兩個節點Ai, Ai' 。需要從每個組中選出一個。而某些點不可以同時選出(稱之為不相容)。任務是保證選出的n個點都能兩兩相容
我們連邊的原則是
假設我們有一個關系,選了x不能y,那么選了x是y那一列只能選y',而選y了則x那一列只能選x'
於是我們就從建兩條邊,x->y',y->x'

我們的如圖,看起來很對稱,於是我們的箭頭關系是選了u就要選v,於是我們進行縮點,對於之后的圖做拓撲序,每一個對應中,選拓撲序前的
於是我們,有了一版代碼。。。
在這里注意要做一個,強連通分量中的對應關系
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int Maxm=50000,Maxn=16008;
struct Node{
int fr,to,lac;
}edge[Maxm],vedge[Maxm];
int vcnt,vh[Maxn],num,col[Maxn],p[Maxn],x,y,cnt,n,m,dfn[Maxn],low[Maxn],dep,sta[Maxn],top,h[Maxn],b[Maxn],a[Maxn];
bool fsta[Maxn],f,flag[Maxn];
void insert(int x,int y){//表示x,y互相仇視
int u=(y^1);
edge[cnt].to=u;
edge[cnt].fr=x;
edge[cnt].lac=h[x];
h[x]=cnt++;
u=(x^1);
edge[cnt].to=u;
edge[cnt].fr=y;
edge[cnt].lac=h[y];
h[y]=cnt++;
}
void tarjan(int u){
dfn[u]=low[u]=++dep;
fsta[u]=1;sta[++top]=u;
for(int i=h[u];i!=-1;i=edge[i].lac){
int to=edge[i].to;
if(dfn[to]){
if(fsta[to]) low[u]=min(low[u],dfn[to]);
continue;
}
tarjan(to);
low[u]=min(low[u],low[to]);
}
if(low[u]==dfn[u]){
num++;
while(fsta[u]){
fsta[sta[top]]=0;
col[sta[top]]=num;
top--;
}
}
}
void vinsert(int x,int y){
vedge[vcnt].fr=x;
vedge[vcnt].to=y;
vedge[vcnt].lac=vh[x];
vh[x]=vcnt++;
}
int main(){
// freopen("sp.in","r",stdin);
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
insert(--x,--y);
}
for(int i=0;i<2*n;i++) if(!dfn[i]) tarjan(i);
for(int i=0;i<2*n;i+=2)
if(col[i]==col[i^1]){
printf("NIE");//判斷無解
return 0;
}
for(int i=0;i<2*n;i++) p[col[i]]=col[i^1];//求其對應
memset(vh,-1,sizeof vh);
for(int i=0;i<cnt;i++){
if(col[edge[i].fr]!=col[edge[i].to]){
vinsert(col[edge[i].to],col[edge[i].fr]);
b[col[edge[i].fr]]++;
}
}
int ans=0;
// 朴素
/*for(int k=1;k<=num/2;k++){
int v=-1;
for(int i=1;i<=num;i++){
if(b[i]==0&&!flag[i]){
v=i;
break;
}
}
if(v==-1) break;
flag[v]=1;
flag[p[v]]=1;
for(int i=0;i<2*n;i++){
if(col[i]==v){
a[++ans]=i;
}
}
for(int i=vh[v];i!=-1;i=vedge[i].lac)
b[vedge[i].to]--;
}
*/
queue<int> q1;
for(int i=1;i<=num;i++) if(b[i]==0) q1.push(i);
while(!q1.empty()){
int v=q1.front();
q1.pop();
if(!flag[v]){
for(int i=0;i<2*n;i++){
if(col[i]==v){
a[++ans]=i;
}
}
for(int i=vh[v];i!=-1;i=vedge[i].lac){
b[vedge[i].to]--;
if(b[vedge[i].to]==0) q1.push(vedge[i].to);
}
}
flag[v]=1;
flag[p[v]]=1;
}
sort(a+1,a+n+1);
for(int i=1;i<=ans;i++){
printf("%d\n",a[i]+1);
}
return 0;
}
這是朴素的\(n^2\)拓撲,我們考慮優化它,可以用隊列,這里就不貼代碼了,
事實上,我們在進行tarjan是其實相當與做了topo,我們考慮下面的輸入
2 2
1 3
1 4
這樣能看出.事實上i是可以指向i'的而對於tarjan我們i'的col一定比i的col小
所以我們考慮輸出i和i'中小的那個
有
for(int i=0;i<2*n;i+=2){
f(col[i]<col[i^1]) printf("%d\n",i+1);
else printf("%d\n",i+2);
}
好了就這樣吧,后面我會補充滿漢這個題的。 於2020.2.6 0:41
eg JSOI 滿漢全席
這題,水呀。。。。。裸的2-sat
但是要注意存在輸出GOOD。。。太坑了
就這樣吧,沒甚么好說的
我有可能后面補一下塔防,BJOI的,但是
看時間吧,畢竟假期不多了
嵬
2-sat,不過是兩個星期六
我困了,兩周之內做第二版
