部分內容轉自luogu神犇題解:https://www.luogu.org/problemnew/solution/P4782
2-SAT定義:
即給定一些布爾方程,每個變量只能爲 or
要求對這些變量進行賦值,滿足布爾方程。
簡單來說就是離散數學中的布爾方程例如:
現在要做的是,爲 ABC 三個變量賦值,滿足三位學生的要求。
但這是 SAT 問題,已被證明爲 NP。
那麼 2-SAT 是什麼呢?
2-SAT,即只有兩個條件,例如布爾方程當中的 沒了,變成了這個樣子:
建圖:
和至少選一個:
和不能同時選:
選了就必須選:
那麼如何解決2-sat問題呢?
用圖論中的強連通分量。
對於每個變量 ,我們建立兩個點: 分別表示變量 取 true 和取 false。所以,圖的節點個數是兩倍的變量個數。在存儲方式上,可以給第 個變量標號爲 ,其對應的反值標號爲 。對於每個同學的要求,轉換爲 。對於這個式子,可以理解爲:「若 a 假則 b 必真,若 b 假則 a 必真」然後按照箭頭的方向建有向邊就好了。綜上,我們這樣對上面的方程建圖.
同一強連通分量內的變量值一定是相等的。也就是說,如果 x 與 在同一強連通分量內部,一定無解。反之,就一定有解了。
但是,對於一組布爾方程,可能會有多組解同時成立。要怎樣判斷給每個布爾變量賦的值是否恰好構成一組解呢?
這個很簡單,只需要 當 所在的強連通分量的拓撲序在 所在的強連通分量的拓撲序之後取 爲真 就可以了。在使用 Tarjan 算法縮點找強連通分量的過程中,已經爲每組強連通分量標記好順序了——不過是反着的拓撲序。所以一定要寫成 。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e6+5;
int dfn[maxn<<1],low[maxn<<1],sta[maxn<<1],vis[maxn<<1],dfnnum;
int stalen;
int n;
int tag[maxn<<1];
struct Edge{
int u,v,nxt;
}edge[maxn<<1];
int head[maxn<<1],tot;
inline void init(){
memset(head,-1,sizeof(head));
tot = 0;
}
inline void addedge(int u,int v){
edge[++tot] = {u,v,head[u]};
head[u] = tot;
}
inline void checkadd(int i,int a,int j,int b){
addedge(i+n*(a&1),j+n*(b^1));
addedge(j+n*(b&1),i+n*(a^1));
}
int num;
void tarjan(int u){
dfn[u] = low[u] = ++dfnnum;
sta[stalen++] = u,vis[u] = 1;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(!dfn[e.v]){
tarjan(e.v);
low[u] = min(low[u],low[e.v]);
}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
}
if(low[u] == dfn[u]){
num++; int cur;
do{
cur = sta[--stalen];
vis[cur] = 0;
tag[cur] = num;
}while(cur!=u);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init();
int m; cin >> n >> m;
for(int i = 0; i < m; ++i){
int ii,a,j,b; cin >> ii >> a >> j >> b;
checkadd(ii,a,j,b);
}
for(int i = 1; i <= 2*n; ++i){
if(!dfn[i]) tarjan(i);
}
for(int i = 1; i <= n; ++i){
if(tag[i]==tag[i+n]){
cout << "IMPOSSIBLE\n";
return 0;
}
}
cout << "POSSIBLE\n";
for(int i = 1; i <= n; ++i){
if(i>1)cout << ' ';
cout << (tag[i] < tag[i+n]);
}
cout << "\n";
return 0;
}
字典序最小的2-sat問題待更新
2-sat問題主要是如何建模,然後求解!
P4171 [JSOI2010]滿漢全席
分析:
把材料如果做成漢式記爲i,如果做爲滿式則記爲,一種材料只能用一種方法做,
所以加邊即可。(具體看代碼)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e2+5;
const int Edgemaxn = 1e3+5;
int n,m;
struct Edge{
int u,v,nxt;
Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[Edgemaxn<<2];
int head[maxn],tot;
int dfn[maxn],low[maxn],sta[maxn],stalen,dfnnum,num;
bool vis[maxn]; int tag[maxn];
inline void init(){
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(tag,0,sizeof(tag));
memset(vis,0,sizeof(vis));
stalen = 0,dfnnum = 0,num = 0;
tot = 0;
}
inline void addedge(int u,int v){
edge[++tot] = Edge(u,v,head[u]);
head[u] = tot;
}
inline void checkadd(int i,int vali,int j,int valj){
addedge(i + n*(vali&1),j + n*(valj^1));
addedge(j + n*(valj&1),i + n*(vali^1));
}
void tarjan(int u){
low[u] = dfn[u] = ++dfnnum;
sta[stalen++] = u,vis[u] = 1;
for(int i = head[u]; ~i ; i = edge[i].nxt){
Edge&e = edge[i];
if(!dfn[e.v]){
tarjan(e.v);
low[u] = min(low[u],low[e.v]);
}else if(vis[e.v])low[u] = min(low[u],dfn[e.v]);
}
if(low[u]==dfn[u]){
num++; int cur;
do{
cur = sta[--stalen];
vis[cur] = 0;
tag[cur] = num;
}while(cur!=u);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while(T--){
init();
cin >> n >> m;;
for(int i = 0; i < m; ++i){
char a,b; int c,d;
cin >> a >> c >> b >> d;
checkadd(c,a=='m',d,b=='m');
}
for(int i = 1; i <= 2*n; ++i){
if(!dfn[i]) tarjan(i);
}
bool isok = true;
for(int i = 1; i <= n; ++i){
if(tag[i]==tag[i+n]){
isok = false;
break;
}
}
if(!isok){
cout <<"BAD" << endl;
continue;
}
cout << "GOOD" << endl;
}
return 0;
}
Now or later UVA - 1146
分析: 相鄰兩個着陸時間間隔最小值要最大,顯然要用到二分,對於當前的mid,我們去建圖,如果相鄰兩個之間着陸時間 ,就沒關係,如果,則需要去連邊,具體看代碼,然後套板子就好了
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e3+5;
struct plane{
int L,R;
plane(int l = 0,int r = 0):L(l),R(r){}
}p[maxn];
int n;
struct Edge{
int u,v,nxt;
Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[maxn*maxn];
int head[maxn<<1],tot;
int dfn[maxn],low[maxn],sta[maxn],tag[maxn],dfnnum,stalen;
bool vis[maxn]; int num;
inline void init(){
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(tag,0,sizeof(tag));
num = stalen = dfnnum = tot = 0;
}
inline void addedge(int u,int v){
edge[++tot] = Edge(u,v,head[u]);
head[u] = tot;
}
inline void checkadd(int u,int valu,int v,int valv){
addedge(u + n*(valu&1),v + n*(valv^1));
addedge(v + n*(valv&1),u + n*(valu^1));
}
inline void tarjan(int u){
dfn[u] = low[u] = ++dfnnum;
sta[stalen++] = u,vis[u] = true;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge&e = edge[i];
if(!dfn[e.v]){
tarjan(e.v);
low[u] = min(low[u],low[e.v]);
}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
}
if(dfn[u]==low[u]){
++num; int cur;
do{
cur = sta[--stalen];
vis[cur] = false;
tag[cur] = num;
}while(cur!=u);
}
}
inline bool check(int mid){
init();
for(int i = 1; i <= n; ++i){
for(int j = i+1; j <= n; ++j){
if(abs(p[i].L - p[j].L) < mid) checkadd(i,1,j,1);
if(abs(p[i].L - p[j].R) < mid) checkadd(i,1,j,0);
if(abs(p[i].R - p[j].L) < mid) checkadd(i,0,j,1);
if(abs(p[i].R - p[j].R) < mid) checkadd(i,0,j,0);
}
}
for(int i = 1; i <= 2*n; ++i){
if(!dfn[i]) tarjan(i);
}
for(int i = 1; i <= n; ++i){
if(tag[i]==tag[i+n])
return false;
}
return true;
}
int main()
{
while(cin >> n){
int l = 0,r = 0;
for(int i = 1; i <= n; ++i){
cin >> p[i].L >> p[i].R;
r = max(r,p[i].R);
}
int ans = 0;
while(l < r){
int mid = l + ((r-l+1)>>1);
if(check(mid)){
l = mid;
}else r = mid - 1;
}
cout << l << endl;
}
return 0;
}
UVALive-3713
這道題至今還沒有AC(個人覺得代碼寫的沒有什麼問題,網上的題解多數已經掛掉了(都是Wrong Answer),筆者先把代碼丟上來,以後再回來看這道題把)
#include <bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 2e5+5;
int age[maxn]; int type[maxn];
struct Edge{
int u,v,nxt;
Edge(int uu = 0,int vv = 0,int nn = 0):u(uu),v(vv),nxt(nn){}
}edge[maxn<<2];
int head[maxn],tot;
int dfn[maxn],sta[maxn],low[maxn],dfnnum,num,stalen;
bool vis[maxn]; int tag[maxn];
inline void init(){
for(int i = 0; i <= 2*n; ++i){
head[i] = -1;
dfn[i] = low[i] = vis[i] = tag[i] = 0;
}
dfnnum = tot = num = stalen = 0;
}
inline void addedge(int u,int v){
edge[++tot] = Edge(u,v,head[u]);
head[u] = tot;
}
inline void checkadd(int u,int valu,int v,int valv){
addedge(u + n*(valu&1),v + n*(valv^1));
addedge(v + n*(valv&1),u + n*(valu^1));
}
inline void tarjan(int u){
dfn[u] = low[u] = ++dfnnum;
sta[stalen++] = u; vis[u] = true;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge& e = edge[i];
if(!dfn[e.v]){
tarjan(e.v);
low[u] = min(low[u],low[e.v]);
}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
}
if(dfn[u]==low[u]){
++num; int cur;
do{
cur = sta[--stalen];
vis[cur] = false;
tag[cur] = num;
}while(cur!=u);
}
}
inline void SAT_2(){
for(int i = 1; i <= 2*n; ++i){
if(!dfn[i]) tarjan(i);
}
bool isok = true;
for(int i = 1; i <= n; ++i){
if(tag[i]==tag[i+n]){
isok = false;
break;
}
}
if(!isok){
printf("No solution.\n");
return;
}
for(int i = 1; i <= n; ++i){
if(tag[i] < tag[i+n]){
printf((type[i]==1? "A\n" : "B\n"));
}
else printf("C\n");
}
}
int main()
{
while(~scanf("%d%d",&n,&m) && n){
init();
int sum = 0;
for(int i = 1; i <= n; ++i){
scanf("%d",age+i); sum += age[i];
}
for(int i = 1; i <= n; ++i){
if(age[i]*n >= sum) type[i] = 1;
else type[i] = 0;
}
for(int i = 0,x,y; i < m; ++i){
scanf("%d%d",&x,&y);
if(type[x]==type[y]){
// 兩者類型一樣
checkadd(x,0,y,0);
}else{
checkadd(x,1,y,1);
}
}
SAT_2();
}
return 0;
}
Codeforces 1215F Radio Stations
建邊1,2是裸的2-sat問題,套板子就行 對於第一種就是 ,所以 (x,0,y,1)(y,0,x,1)
對於第二種就是兩個不能同時選 (y,1,x,0)(x,1,y,0)
那麼對於第三個該怎麼解決?
對於區間 [L,R], 那麼 滿足(爲1), 不滿足(爲0) 且 若必須取1。