2-SAT

    前天打了一场codeforces,发现c题是一道2-sat题,然而我并不会打,于是就爆炸.于是现在就仔细学一发2-sat.

    首先把每个变量拆成2个点,一个表示它为0,一个表示它为1.把所有限制条件拆成"如果i的值是a,j的值必须是b"这样的条件,然后从表示i的值为a的点向表示j的值为b的点连一条边,再从表示j的值为!b的点向表示i的值为!a的点连一条边.

    然后跑tarjan缩点.跑完之后如果对于某个变量,表示它为0的点和表示它为1的点在同一个连通分量了就无解,否则肯定有解.

    然后接下来网上的一些写法需要对缩过点的图进行拓扑排序,然而我从zrf大佬的博客得知tarjan出来的顺序就是反过来的拓扑序,于是就可以省去这个步骤,用一些更高妙的方法求.

    对于每个变量,如果它为0的点所在的连通分量的编号大于它为1的点所在的连通分量的编号,该变量的值就为1,否则就为0.

    用这个方法打了一遍codeforces875C,比我比赛时从网上乱搞来的2-sat板子短多了.

    875C的代码在下面.

#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
const int maxn=200000;
vector<int> a[maxn+10];
int n,m;
vector<int> G[maxn+10];
bool value[maxn+10];
int dfn[maxn+10],low[maxn+10],scc[maxn+10],scc_cnt,dfs_cnt;
stack<int> S;
int ans;
void dfs(int x){
    dfn[x]=low[x]=++dfs_cnt; S.push(x);
    for(int i=0;i<G[x].size();++i){
        int e=G[x][i];
        if(!dfn[e]){
            dfs(e); low[x]=min(low[x],low[e]);
        }else if(!scc[e]) low[x]=min(low[x],dfn[e]);
    }
    if(low[x]==dfn[x]){
        ++scc_cnt;
        for(;;){
            int t=S.top(); S.pop();
            scc[t]=scc_cnt; if(x==t) break;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        int l; scanf("%d",&l); a[i].resize(l);
        for(int j=0;j<l;++j) scanf("%d",&a[i][j]);
    }
    for(int i=1;i<n;++i){
        for(int j=0;j<a[i].size();++j)
            if(j>=a[i+1].size()){
                printf("No\n"); return 0;
            }else if(a[i][j]!=a[i+1][j]){
                int l=a[i][j],r=a[i+1][j];
                if(l<r){
                    G[l<<1].push_back(r<<1);
                    G[(r<<1)-1].push_back((l<<1)-1);
                }else{
                    G[l<<1].push_back((l<<1)-1);
                    G[(r<<1)-1].push_back(r<<1);
                }
                break;
            }
    }    
    for(int i=1;i<=m<<1;++i) if(!dfn[i]) dfs(i);
    for(int i=1;i<=m;++i) if(scc[i<<1]==scc[(i<<1)-1]){
        printf("No\n"); return 0;
    }else ans+=(value[i]=(scc[i<<1]>scc[(i<<1)-1]));
    printf("Yes\n%d\n",ans);
    for(int i=1;i<=m;++i) if(value[i]) printf("%d ",i);
    return 0;
}

 

posted @ 2017-10-18 16:14  jxcakak  阅读(303)  评论(0编辑  收藏  举报