標籤:效果 else isp 分享圖片 line etc head ace 表示
一、前置技能
> Tarjan求割點
> 高中數學選修2-1 命題與邏輯關係(好像是這個名字)
> 莓了
(本文用&&表示與 || 表示或 !x表示x的否命題 與正常的數學符號不同食用請注意)
二、k-SAT
> Q: 為什麼講2-SAT之前要先講k-SAT?
因為k-SAT被證明是NP-完全問題
所以暴搜就不去管它……
> Q: 所以什麼是“SAT”?
由美國大學委員會主辦的一場考試
SAT被稱為布爾方程的可滿足性問題
就是判斷一組邏輯關係同時滿足的正確性……
比如a&&(!a) 就不滿足
再比如a&&b||c&&!d&&(.....)
我們給一種特殊的布爾方程起一個名字叫 合取範式
就是形如(a || !b || c) && (d || e) && (...)
其中每個bool變數叫 文字 打了括弧的部分就叫 子句
如果每個子句大小不超過k那麼就叫 k-SAT
同理 如果合取範式的每個子句中的文字個數都不超過兩個,那麼對應的 SAT 問題又稱為 2-SAT 問題。
三、解法
考慮一個二分圖 左邊表示bool變數值為0(偽) 右邊表示值為1(真)
如果滿足一個命題必然能推出另一個命題 那麼就在這兩個命題之間連一條邊
考慮兩個bool變數之間的關係無非是 && 和 ||
由於我們是2-SAT 所以最多出現a || b兩個變數
那麼轉化為蘊含(推出)關係
由 || 運算的定義
!a => b 且 !b => a
然後是 &&
!a => a 且 !b => b
> Q: 這……bool變數還能同時滿足真和偽?
當然不是 這裡的推出 可以理解為如果選擇了a為偽,那麼最後會推出a為真
所以我們最後選擇的時候不能選擇a為偽 只能選擇a為真 達到了我們的目的
> Q: 好吧……可是,為什麼要這麼建圖?
我們考慮bool運算的性質
命題成立則它的逆否命題成立 這個性質又叫 對稱性
同時考慮推出的 傳遞性 ,如果b是由某一個變數推知為真 和它為真的效果是一樣的 都能推知下一步的變數
所以拆完真偽最終在一個強連通分量裡的點的bool值相同(注意並不是變數的bool值相同)
同時上一個Q表示建圖可以處理單個變數定值的問題
> Q:嗯……如何求出最終答案呢?
我們對於最後建好的圖跑一個Tarjan縮點 如果變數的真值和偽值在同一個強連通分量裡(即既不能為真也不能為偽)那麼就無解
否則 每個點的答案應該是真值和偽值中拓撲序靠後的那個(同第一個Q)
(所以要縮點)
然後由於我們跑tarjan的時候 就是拓撲序靠後的強連通分量編號小
所以就不用建新圖辣
四、代碼
Code:(luogu P4782)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 #define itn int 6 #define ms(a,b) memset(a,b,sizeof a) 7 #define rep(i,a,n) for(int i = a;i <= n;i++) 8 #define per(i,n,a) for(int i = n;i >= a;i--) 9 using namespace std;10 typedef long long ll;11 ll read() {12 ll as = 0,fu = 1;13 char c = getchar();14 while(c < ‘0‘ || c > ‘9‘) {15 if(c == ‘-‘) fu = -1;16 c = getchar();17 }18 while(c >= ‘0‘ && c <= ‘9‘) {19 as = as * 10 + c - ‘0‘;20 c = getchar();21 }22 return as * fu;23 }24 const int N = 2000005;25 //head26 int n,m;27 int head[N],nxt[N<<1],mo[N<<1],cnt = -1;28 bool flag[N];29 void add(itn x,int y) {30 mo[++cnt] = y;31 nxt[cnt] = head[x];32 head[x] = cnt;33 }34 int rev(int x) {return x>n?x-n:x+n;}35 36 int dfn[N],low[N],col[N],stk[N];37 int scc,idx,top;38 bool ins[N];39 void tarjan(int x) {40 dfn[x] = low[x] = ++idx;41 stk[++top] = x,ins[x] = 1;42 for(int i = head[x];i;i = nxt[i]) {43 int sn = mo[i];44 if(!dfn[sn]) {45 tarjan(sn);46 low[x] = min(low[x],low[sn]);47 } else if(ins[sn]) low[x] = min(low[x],dfn[sn]);48 }49 if(dfn[x] == low[x]) {50 int t = -1;51 scc++;52 while(t!=x) {53 t = stk[top--];54 col[t] = scc;55 ins[t] = 0;56 }57 }58 }59 60 int main() {61 n = read();62 m = read();63 rep(i,1,m) {64 int x = read();x += read()*n;65 int y = read();y += read()*n;66 add(rev(x),y),add(rev(y),x);67 }68 rep(i,1,n<<1) if(!dfn[i]) tarjan(i);69 bool flag = 1;70 rep(i,1,n) if(col[i] == col[n+i]) flag = 0;71 if(!flag) puts("IMPOSSIBLE");72 else {73 puts("POSSIBLE");74 rep(i,1,n) printf("%d ",col[i] > col[i+n]);75 puts("");76 }77 return 0;78 }View Code
注意:
1.跑的時候依然像Tarjan一樣考慮多個聯通塊
2.本題全部是或的關係 注意反向建圖
3.最後比較的時候每個人寫法不一樣要考慮大於還是小於
例題詳見Practice 1
By prophetB
2-SAT 入門