2-SAT模板(scc強連通模板)-poj3683-Priest John's Busiest Day



我最愛的《挑戰程序設計競賽麼麼噠》上的例題

先放一下2-SAT模板。

也就是給一堆布爾變量(就是要麼是真的要麼是假的的一堆變量),和一堆布爾方程,問能否通過選擇這些變量是真的還是假的,使得所有布爾方程都成立。


注意一下,因爲是用scc跑強連通,所以下面這個既是2sat,又是強連通的模板。

強連通還有tarjan方法可以求,這是模板地址tarjan強連通模板


講一下scc求強連通分量的原理

1、這是原圖,這幅圖裏面ABC屬於一個強連通分量


2、跑一遍dfs。終點的序號爲1,起點的序號爲n。

3、所有的邊反向。所以在當初加邊的時候!一定要加了正向邊,又加上反向邊!

4、然後根據序號從大到小處理每個頂點。紅的表示正在走,藍的表示已經走過。

scc函數從頂點六開始走,rdfs函數發現它不存在反向邊。那麼頂點6獨自屬於一個強連通分量。

接下來scc函數從頂點五開始走,rdfs函數發現它存在反向邊指向6,但是剛剛已經訪問過了啦!所以它還是自己屬於一個強連通分量。

scc函數從頂點4開始走,rdfs函數發現它反向邊的頂點指向一個不曾訪問國過的點,那麼繼續往下rdfs,直到到達某個節點(比如C),它所有反向邊指向的節點都訪問過了。

5、這樣子先dfs一次。再按照序號從大到小逐漸rdfs(也就是反向邊的dfs一次),就可以找到所有的強連通分量。

總的複雜度是O(V+E)。



int V;
int used[MAX_N];
vector<int> vs;
int cmp[MAX_N];
void dfs(int ver)
{
//printf("ver = %d\n",ver);
    used[ver] = 1;
    for (int i = head[ver]; i != -1; i = pra[i].next){
        int u = pra[i].v;
        if (!used[u])
            dfs(u);
    }
    vs.push_back(ver);
}

void rdfs(int ver,int k){//printf("ver = %d k = %d\n",ver,k);
    used[ver] = 1;
    cmp[ver] = k;
    for (int i = rehead[ver]; i != -1; i = re[i].next){
        int u = re[i].v;
        if (!used[u])
            rdfs(u,k);
    }
}


int scc()
{
    memset(used,0,sizeof(used));
    vs.clear();
    for (int v = 0; v < V; v++){
        if (!used[v]) dfs(v);
    }
    memset(used,0,sizeof(used));
    int k = 0;
    for (int i = vs.size()-1; i >= 0; i--){
    //printf("i=%d vector= %d",i,vs[i]);
        if (!used[vs[i]])
            rdfs(vs[i],k++);
    }
    return k;
}
//因爲連邊之後跑一遍scc,所以記得有反向邊的!


題意:有一個神父去主持婚禮,要麼在開始的時候主持,要麼在結束的時候主持。問你他能否參加小鎮上的所有婚禮而不互相沖突

解答:對於婚禮A,把主持開頭設爲A爲真,把主持結尾設爲非A爲真。然後呢,根據已知的時間互相連邊。連線方向是已知的推出未知。
比如已知a和B非不能同時存在,那麼a可以推出b,連一個箭頭。非b可以推出非a,非b連向非a

連邊完成後跑一遍SCC判斷強連通如果後來A和非A處在同一個強連通分量裏面,那就會衝突,反之,輸出yes。

對於輸出結果。任取一個強連通分量爲真,比如對於婚禮a,輸出強連通分量標號較大的那個事件就可以了。

上代碼

/*注意反向邊,反向圖,init處理反向的head;
RE了。算n×n當做邊數好了
還是RE了。算2n×2n當做邊數好了
5000000的int數組佔用三萬多的內存。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAX_N 10000
#define SIZE_M 4000005
using namespace std;
int N;
int S[MAX_N],T[MAX_N],D[MAX_N];

struct pp
{
    int v,next,w;
}pra[SIZE_M],re[SIZE_M];
int e,head[MAX_N],rehead[MAX_N];
void init()
{
    e = 0;
    memset(head,-1,sizeof(head)    );
    memset(rehead,-1,sizeof(rehead)    );
}
void addedge(int x,int y)//,int z1,int z2)
{
    pra[e].v = y;
    pra[e].next = head[x];
    head[x] = e;

    re[e].v = x;
    re[e].next = rehead[y];
    rehead[y] = e++;

}

int V;
int used[MAX_N];
vector<int> vs;
int cmp[MAX_N];
void dfs(int ver)
{
//printf("ver = %d\n",ver);
    used[ver] = 1;
    for (int i = head[ver]; i != -1; i = pra[i].next){
        int u = pra[i].v;
        if (!used[u])
            dfs(u);
    }
    vs.push_back(ver);
}

void rdfs(int ver,int k){//printf("ver = %d k = %d\n",ver,k);
    used[ver] = 1;
    cmp[ver] = k;
    for (int i = rehead[ver]; i != -1; i = re[i].next){
        int u = re[i].v;
        if (!used[u])
            rdfs(u,k);
    }
}


int scc()
{
    memset(used,0,sizeof(used));
    vs.clear();
    for (int v = 0; v < V; v++){
        if (!used[v]) dfs(v);
    }
    memset(used,0,sizeof(used));
    int k = 0;
    for (int i = vs.size()-1; i >= 0; i--){
    //printf("i=%d vector= %d",i,vs[i]);
        if (!used[vs[i]])
            rdfs(vs[i],k++);
    }
    return k;
}
//因爲連邊之後跑一遍scc,所以記得有反向邊的!


/*連線方向是已知的推出未知 其他的不用管了哈哈哈哈
比如已知a和B非不能同時存在,那麼a可以推出b,連一個箭頭。非b可以推出非a,非b連向非a

2-sat的原理就是。
在可以互相發生的事情之間連一條邊,然後

*/

void solve()
{
    //設a是i,非a是i+N;b是j,非b是b+N

    V = 2*N;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < i; j++){//i > j, 連單向邊,兩邊方式不同
        //for (int j = 0; j < N; j++){
       // printf("i=%d j = %d",i,j);
            if (min(S[i]+D[i], S[j]+ D[j])> max(S[i],S[j])){//由當前的a可以推出b非。由當前的b可以推出a非
                addedge(i,j+ N);//從上往下連
                addedge(j,i+N);// 從下往上連
                //printf("ss i=%d j = %d\n",i,j);
            }
            if (min(S[i]+D[i],T[j] )> max(S[i],T[j]- D[j])){//由當前的a可以推出b,由當前的b非可以推出a非
                addedge(i, j);
                addedge(j+ N, i + N);
                //printf("s ti=%d j = %d\n",i,j);
            }
            if (min(T[i],T[j])> max(T[i]-D[i],T[j] - D[j])){//由當前的a非可以推出b,由當前的b非可以推出a
                addedge(i+N, j);
                addedge(j+N,i);
                //printf("tt i=%d j = %d\n",i,j);
                /*addedge(i,j+ N);
                addedge(j,i+ N);*/
            }
            if (min( T[i],S[j]+ D[j])> max(S[j], T[i] - D[i])){//由當前的a非可以推出b非,由當前的b可以推出a
                addedge(i+ N, j+N);
                addedge(j,i);
                //printf("tsi=%d j = %d\n",i,j);
                /*addedge(i,j);
                addedge(j + N, i + N);*/
            }
        }

    scc();
    /*for (int i = 0; i < V; i++){
        //printf("i=%d               ",i);
        for (int j = head[i]; j != -1; j = pra[j].next)
            printf(" %d temp=%d",pra[j].v,cmp[pra[j].v]);
        puts("");
    }*/
    for (int i = 0; i < N; i++)
        if (cmp[i] == cmp[i+N]){
        //printf("i=%d\n",i);
            puts("NO");
            return;
        }

    puts("YES");
    for (int i = 0; i < N; i++){
        if (cmp[i] > cmp[i+N]){//  任取一個聯通分量設爲真。看當前a還是非a在這個聯通分量中
            printf("%02d:%02d %02d:%02d\n",S[i]/60,S[i]%60,(S[i]+ D[i])/60,(S[i]+ D[i])%60);
        }else
            printf("%02d:%02d %02d:%02d\n",(T[i] - D[i])/60,(T[i] - D[i])%60,T[i]/60,T[i]%60);
    }
}


int main()
{
    //freopen("input.txt","r",stdin);
    int sum=0;
    while (~scanf("%d",&N)){
        init();
        int temp1,temp2,temp3,temp4,temp5;
        for (int i = 0; i < N ;i++){
            scanf("%d:%d %d:%d %d",&temp1,&temp2,&temp3,&temp4,&temp5);
            S[i] = temp1*60 + temp2;
            T[i] = temp3 * 60 + temp4;
            D[i] = temp5;
            //printf("%d %d %d\n",S[i],T[i],D[i]);
        }
        solve();
        //printf("%d\n",sum++);
    }
    return 0;

}


/*
2
08:00 09:00 30
08:15 09:00 20

*/










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章