2-sat問題,輸出方案,幾種方法(趙爽的論文染色解法+其完全改進版)淺析 / POJ3683
本文原創於 2014-02-12 09:26。 今複習之用,有新體會,故重新編輯。
2014-02-12 09:26:
2-sat之第二斬!昨天看了半天論文(趙爽的和俉昱的),終於看明白了!好激動有木有!終於理解了趙爽的每一句話!並且用了200+行程式碼實現,A了!具體過程我是敲了幫天的程式碼啊!!!不容易啊!步驟如下:
把相關問題編號為01 23 45....,(每個編號為一個命題)奇數為取,偶數不取,那麼相鄰倆個互逆,於是根據具體情況(check)一下,建立圖,tarjan判斷有無解,然後順便再縮點,重新建圖(逆圖),在對新圖拓撲,仔細閱讀下面趙爽的話:理解每一句:
如果沒有產生矛盾,我們就可以把處在同一個強連通分量中的點和邊縮成一個點,得到
新的有向圖G。然後,我們把G中的所有弧反向,得到圖G ′ ′ ′′。
現在我們觀察 。由於已經進行了縮點的操作,因此 G′′ G′′中一定不存在圈,也就是說,
具有拓撲結構。 G′′
我們把G中所有頂點置為“未著色”。按照拓撲順序重複下面的操作: ′′ 是啊,先對新圖(逆的)拓撲,儲存起來,然後開始染色,對每個染成“不選”的還要對其子孫也不選 擇,(再次dfs。。。無奈),廢了半天啊!!!!下面第一段程式碼便是!!
1、 選擇第一個未著色的頂點x。把x染成紅色。
2、 把所有與x矛盾的頂點 (如果存在bb yjjB ¬ ∈ ,且b屬於 j
x代表的強連
通分量, j
b ¬ 屬於 代表的強連通分量,那麼 y x和 就是互相矛盾的頂點)
及其子孫全部全部染成藍色。
y
3、 重複操作1和2,直到不存在未著色的點為止。此時,G′′中被染成紅色的
點在圖G中對應的頂點集合,就對應著該2-SAT的一組解。
後來在大牛交流中,發現無需拓撲啊!白痴啊!盡在眼前還去自己寫什麼??!!瞭解到:每個強連通分量都是在它的所有後繼強連通分量被求出之後求得的。因此,如果將同一強連通分量收縮為一個結點而構成一個有向無環圖,這些強連通分量被求出的順序是這一新圖的逆拓撲序!!!!
不用再次新圖拓撲啊!!!何必多此一舉!於是來了第二個程式碼!!
還沒完???的確,染色?大牛證明了(現在證明看來也很容易的),無須如此!直接tarjan即可!詳見程式碼三!!又簡單了許多啊!從此,2-sat輸出方案,哦?不用怕!!!!so easy!
繼續刷幾題,練練新劍!
今//三種程式碼:一次比一次簡單,第一次完全按論文進行模擬的,比較繁瑣,但是思路清晰,包括倆次建圖+拓撲+染色+tarjan+dfs,
建圖是關鍵,每次新增的邊要互為假言易位式(一對),最後一種方法最妙,以後都用這樣的方法,簡單又快捷;
該題題意:某一天結婚的人特別多但是主持婚禮的神父只有一個。婚禮時間從s開始到e結束,神父必須在s到s+d或者e-d到e這段時間內在。給定了n個婚禮的s,e,d,求一種方案能使得神父主持所有的婚禮。
思路:建圖簡單,資料處理一下,按編號儲存,之後:遍歷點,取矛盾的點新增假言易位邊,縮點(同一個SCC中必然可以互推)來判斷有無解,輸出方案的話,只需新圖(不必真的建),每次取逆拓撲小的(scc[i]小的命題即可)(反證即可)。
#include<iostream> //5340K 360MS
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<cmath>
using namespace std;
int n;const int MAX=2001;
struct points //點,01,23,45.。。相連為一對,x^1取對應點(改變奇偶性)
{
int from,end;
};
points point[MAX];
int low[MAX];int dfn[MAX];int visited[MAX];bool is_instack[MAX];stack<int>s;
int times=0; int scc[MAX]; int numblock;
int indgree[MAX]; int tuopoxuliu[MAX]; int color[MAX]; //入度,tuopo序列,染色
vector<int>ans(MAX); //最終答案
vector<vector<int> >edges(MAX); //原圖
vector<vector<int> >newgraph(MAX); //新圖
vector<vector<int> >SCC(MAX); //儲存SCC【i】含有的點
void initialize()
{
numblock=times=0;
for(int i=0;i<2*n;i++)
{
tuopoxuliu[i]=color[i]=visited[i]=low[i]=dfn[i]=is_instack[i]=0;
edges[i].clear();
scc[i]=-1;
}
}
void tarjan(int u) //有向圖dfs,這個不解釋
{
low[u]=dfn[u]=++times;
is_instack[u]=1;
s.push(u);
int len=edges[u].size();
for(int i=0;i<len;i++)
{
int v=edges[u][i];
if(visited[v]==0)
{
visited[v]=1;
tarjan(v);
if(low[u]>low[v])low[u]=low[v];
}
else if(is_instack[v]&&dfn[v]<low[u])
{
low[u]=dfn[v];
}
}
if(dfn[u]==low[u])
{
numblock++;
int cur;
do
{
cur=s.top();
is_instack[cur]=0;
s.pop();
scc[cur]=numblock;
SCC[numblock].push_back(cur); //每個SCC對應哪些點儲存起來
}while(cur!=u);
}
}
bool agst(points a,points b) //判斷矛盾的點
{
if(a.from<=b.from&&a.end>b.from) //注意==號的判定!別因為這個跪了!
return true;
if(b.from<=a.from&&b.end>a.from)
return true;
return false;
}
bool build_graph_has_solution() //建圖
{
initialize();
for(int i=0;i<2*n;i++)
for(int j=i+1;j<2*n;j++)
{
if(((i>>1)!=(j>>1))&&agst(point[i],point[j])) //有時間衝突
{
if(agst(point[i],point[j^1])) //和另一個也矛盾,那麼i不能選(用A->非A表示)
edges[i].push_back(i^1);
else
{
edges[i].push_back(j^1); //那麼選你沒我
edges[j].push_back(i^1);
}
}
}
for(int i=0;i<2*n;i++)
{
if(visited[i]==0)
{
visited[i]=1;
tarjan(i);
}
}
for(int i=0;i<2*n;i+=2)
{
if(scc[i]==scc[i+1]) //矛盾的點在一個SCC中,
{
printf("NO\n");
return false;
}
}
return true;
}
void tuopu() //新圖拓撲,記錄拓撲序列(1-numblock)儲存之
{
stack<int>sta;
int count=1;
for(int i=1;i<=numblock;i++) //入度點0點
if(indgree[i]==0)
sta.push(i);
while(!sta.empty())
{
int cur=sta.top();
sta.pop();
tuopoxuliu[count++]=cur;
int len4=newgraph[cur].size(); //新圖,其孩子入度--
for(int i=0;i<len4;i++)
{
indgree[newgraph[cur][i]]--;
if(indgree[newgraph[cur][i]]==0)
sta.push(newgraph[cur][i]);
}
}
}
void dfs_unchoose(int u) //u及其子孫都不選
{
int len5=newgraph[u].size();
for(int i=0;i<len5;i++)
{
int v=newgraph[u][i];
if(color[v]!=2)
{
color[v]=2;
dfs_unchoose(v);
}
}
}
void solve()
{
for(int i=0;i<2*n;i++) //建立新圖(逆圖,有向無環)
{
int len=edges[i].size();
for(int j=0;j<len;j++)
{
int v=edges[i][j];
bool mark=0;
if(scc[i]!=scc[v]) //是新圖的邊 //注意下面哪些是SCC[]
{
int len2=newgraph[scc[v]].size(); //刪去新圖重邊(要判斷入度)
for(int k=0;k<len2;k++)
{
if(newgraph[scc[v]][k]==scc[i]){mark=1;break;}
}
if(mark)continue;
newgraph[scc[v]].push_back(scc[i]); //逆圖
indgree[scc[i]]++;
}
}
}
tuopu();
for(int i=1;i<=numblock;i++) //開始染色,
{
int cur=tuopoxuliu[i];
if(color[cur]==0) //0未染色
{
color[cur]=1; //標記選擇
int len3=SCC[cur].size(); //SCC中,
for(int j=0;j<len3;j++)
{
color[scc[SCC[cur][j]^1]]=2; //這些點矛盾的點所在的SCC標記為2(不選).
dfs_unchoose(scc[((SCC[cur][j])^1)]); //其子孫也不選
}
}
} //染色完畢
for(int i=1;i<=numblock;i++) //統計ans
{
if(color[i]==1) //在同一個SCC中全要
{
int len6=SCC[i].size();
for(int j=0;j<len6;j++)
{
ans[SCC[i][j]/2]=SCC[i][j];
}
}
}
printf("YES\n");
for(int i=0;i<n;i++)
{
int hour=point[ans[i]].from/60;int miu=point[ans[i]].from%60;
printf("%02d:%02d ",hour,miu);
hour=point[ans[i]].end/60; miu=point[ans[i]].end%60;
printf("%02d:%02d\n",hour,miu);
}
}
void readin()
{
for(int i=0;i<n;i++)
{
int a1,b1,a2,b2,d;char c;
scanf("%d%c%d",&a1,&c,&b1); scanf("%d%c%d",&a2,&c,&b2); scanf("%d",&d);
point[i*2].from=a1*60+b1; point[i*2].end=a1*60+b1+d;
point[i*2+1].from=a2*60+b2-d; point[i*2+1].end=a2*60+b2;
}
}
int main()
{
scanf("%d",&n);
readin();
if( build_graph_has_solution())
solve();
}
.
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<cmath>
using namespace std;
int n;const int MAX=2001;
struct points //點,01,23,45.。。相連為一對,x^1取對應點(改變奇偶性)
{
int from,end;
};
points point[MAX];
int low[MAX];int dfn[MAX];int visited[MAX];bool is_instack[MAX];stack<int>s;
int times=0; int scc[MAX]; int numblock;
int color[MAX]; //染色
vector<int>ans(MAX); //最終答案
vector<vector<int> >edges(MAX); //原圖
vector<vector<int> >newgraph(MAX); //新圖
vector<vector<int> >SCC(MAX); //儲存SCC【i】含有的點
void initialize()
{
numblock=times=0;
for(int i=0;i<2*n;i++)
{
color[i]=visited[i]=low[i]=dfn[i]=is_instack[i]=0;
edges[i].clear();
scc[i]=-1;
}
}
void tarjan(int u) //有向圖dfs,這個不解釋
{
low[u]=dfn[u]=++times;
is_instack[u]=1;
s.push(u);
int len=edges[u].size();
for(int i=0;i<len;i++)
{
int v=edges[u][i];
if(visited[v]==0)
{
visited[v]=1;
tarjan(v);
if(low[u]>low[v])low[u]=low[v];
}
else if(is_instack[v]&&dfn[v]<low[u])
{
low[u]=dfn[v];
}
}
if(dfn[u]==low[u])
{
numblock++;
int cur;
do
{
cur=s.top();
is_instack[cur]=0;
s.pop();
scc[cur]=numblock;
SCC[numblock].push_back(cur); //每個SCC對應哪些點儲存起來
}while(cur!=u);
}
}
bool agst(points a,points b) //判斷矛盾的點
{
if(a.from<=b.from&&a.end>b.from) //注意==號的判定!別因為這個跪了!
return true;
if(b.from<=a.from&&b.end>a.from)
return true;
return false;
}
bool build_graph_has_solution() //建圖
{
initialize();
for(int i=0;i<2*n;i++)
for(int j=i+1;j<2*n;j++)
{
if(((i>>1)!=(j>>1))&&agst(point[i],point[j])) //有時間衝突
{
if(agst(point[i],point[j^1])) //和另一個也矛盾,那麼i不能選(用A->非A表示)
edges[i].push_back(i^1);
else
{
edges[i].push_back(j^1); //那麼選你沒我
edges[j].push_back(i^1);
}
}
}
for(int i=0;i<2*n;i++)
{
if(visited[i]==0)
{
visited[i]=1;
tarjan(i);
}
}
for(int i=0;i<2*n;i+=2)
{
if(scc[i]==scc[i+1]) //矛盾的點在一個SCC中,
{
printf("NO\n");
return false;
}
}
return true;
}
void dfs_unchoose(int u) //u及其子孫都不選
{
int len5=newgraph[u].size();
for(int i=0;i<len5;i++)
{
int v=newgraph[u][i];
if(color[v]!=2)
{
color[v]=2;
dfs_unchoose(v);
}
}
}
void solve()
{
for(int i=0;i<2*n;i++) //建立新圖(逆圖,有向無環)
{
int len=edges[i].size();
for(int j=0;j<len;j++)
{
int v=edges[i][j];
bool mark=0;
if(scc[i]!=scc[v]) //是新圖的邊 //注意下面哪些是SCC[]
{
int len2=newgraph[scc[v]].size(); //刪去新圖重邊(要判斷入度)
for(int k=0;k<len2;k++)
{
if(newgraph[scc[v]][k]==scc[i]){mark=1;break;}
}
if(mark)continue;
newgraph[scc[v]].push_back(scc[i]); //逆圖
}
}
}
for(int i=1;i<=numblock;i++) //開始染色,
{
int cur=i;
if(color[cur]==0) //0未染色
{
color[cur]=1; //標記選擇
int len3=SCC[cur].size(); //SCC中,
for(int j=0;j<len3;j++)
{
color[scc[SCC[cur][j]^1]]=2; //這些點矛盾的點所在的SCC標記為2(不選).
dfs_unchoose(scc[((SCC[cur][j])^1)]); //其子孫也不選
}
}
} //染色完畢
for(int i=1;i<=numblock;i++) //統計ans
{
cout<<i<<": "<<endl;
int len6=SCC[i].size();
for(int j=0;j<len6;j++)
{
cout<<SCC[i][j]<<" ";
cout<<endl;
if(color[i]==1) //在同一個SCC中全要
{
cout<<"get:";cout<<SCC[i][j]<<endl;
ans[SCC[i][j]/2]=SCC[i][j];
}
}
}
printf("YES\n");
for(int i=0;i<n;i++)
{
int hour=point[ans[i]].from/60;int miu=point[ans[i]].from%60;
printf("%02d:%02d ",hour,miu);
hour=point[ans[i]].end/60; miu=point[ans[i]].end%60;
printf("%02d:%02d\n",hour,miu);
}
}
void readin()
{
for(int i=0;i<n;i++)
{
int a1,b1,a2,b2,d;char c;
scanf("%d%c%d",&a1,&c,&b1); scanf("%d%c%d",&a2,&c,&b2); scanf("%d",&d);
point[i*2].from=a1*60+b1; point[i*2].end=a1*60+b1+d;
point[i*2+1].from=a2*60+b2-d; point[i*2+1].end=a2*60+b2;
}
}
int main()
{
scanf("%d",&n);
readin();
if( build_graph_has_solution())
solve();
}
#include<iostream> //無需自己拓撲!無需染色!無需重新建圖!屌!以後不用怕了!直接秒殺!
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<cmath>
using namespace std;
int n;const int MAX=2001;
struct points //點,01,23,45.。。相連為一對,x^1取對應點(改變奇偶性)
{
int from,end;
};
points point[MAX];
int low[MAX];int dfn[MAX];int visited[MAX];bool is_instack[MAX];stack<int>s;
int times=0; int scc[MAX]; int numblock;
vector<int>ans(MAX); //最終答案
vector<vector<int> >edges(MAX); //原圖
void initialize()
{
numblock=times=0;
for(int i=0;i<2*n;i++)
{
visited[i]=low[i]=dfn[i]=is_instack[i]=0;
edges[i].clear();
scc[i]=-1;
}
}
void tarjan(int u) //有向圖dfs,這個不解釋
{
low[u]=dfn[u]=++times;
is_instack[u]=1;
s.push(u);
int len=edges[u].size();
for(int i=0;i<len;i++)
{
int v=edges[u][i];
if(visited[v]==0)
{
visited[v]=1;
tarjan(v);
if(low[u]>low[v])low[u]=low[v];
}
else if(is_instack[v]&&dfn[v]<low[u])
{
low[u]=dfn[v];
}
}
if(dfn[u]==low[u])
{
int cur; numblock++;
do
{
cur=s.top();
is_instack[cur]=0;
s.pop();
scc[cur]=numblock;
}while(cur!=u);
}
}
bool agst(points a,points b) //判斷矛盾的點
{
if(a.from<=b.from&&a.end>b.from) //注意==號的判定!別因為這個跪了!
return true;
if(b.from<=a.from&&b.end>a.from)
return true;
return false;
}
bool build_graph_has_solution() //建圖
{
initialize();
for(int i=0;i<2*n;i++)
for(int j=i+1;j<2*n;j++)
{
if(((i>>1)!=(j>>1))&&agst(point[i],point[j])) //有時間衝突
{
if(agst(point[i],point[j^1])) //和另一個也矛盾,那麼i不能選(用A->非A表示)
edges[i].push_back(i^1);
else
{
edges[i].push_back(j^1); //那麼選你沒我
edges[j].push_back(i^1);
}
}
}
for(int i=0;i<2*n;i++)
{
if(visited[i]==0)
{
visited[i]=1;
tarjan(i);
}
}
for(int i=0;i<2*n;i+=2)
{
if(scc[i]==scc[i+1]) //矛盾的點在一個SCC中,
{
printf("NO\n");
return false;
}
}
return true;
}
void solve()
{
for(int i=0;i<2*n;i+=2) //統計ans
{
if(scc[i]<scc[i+1]) //關鍵!!這樣選擇!!
ans[i/2]=i;
else
ans[i/2]=i+1;
}
printf("YES\n");
for(int i=0;i<n;i++) //還原
{
int hour=point[ans[i]].from/60;int miu=point[ans[i]].from%60;
printf("%02d:%02d ",hour,miu);
hour=point[ans[i]].end/60; miu=point[ans[i]].end%60;
printf("%02d:%02d\n",hour,miu);
}
}
void readin()
{
for(int i=0;i<n;i++)
{
int a1,b1,a2,b2,d;char c;
scanf("%d%c%d",&a1,&c,&b1); scanf("%d%c%d",&a2,&c,&b2); scanf("%d",&d);
point[i*2].from=a1*60+b1; point[i*2].end=a1*60+b1+d;
point[i*2+1].from=a2*60+b2-d; point[i*2+1].end=a2*60+b2;
}
}
int main()
{
scanf("%d",&n);
readin();
if( build_graph_has_solution())
solve();
}