题解 P4782 【【模板】2-SAT 问题】

题目链接:Link

Solution

将每一个点\(x_i\)拆成\(2i\)\(2i+1\)\(2i\)表示\(x_i\)的假状态,\(2i+1\)表示\(x_i\)的真状态),问题就化为了从2n个状态中选出原来的每一个\(x_i\)的一个状态。

这有什么用呢?别急,先举个栗子,“1 1 3 0”就可以用有向边 2->6 7->3 来表示,什么意思呢?2表示1为false,根据规则可得3一定为false(也就是6),7表示3为true,根据规则可得1一定为true。那么就可以对这个有向图的每个连通分量(不一定是强连通的)进行dfs处理,并加上剪枝、回溯就行了,细节见代码。

注意:随机数据时dfs 2-SAT跑得很快,但对于大规模的数据,最好还是使用Tarjan求2-SAT

// luogu-judger-enable-o2
#include<cstdio>
#include<vector>
using namespace std;
template<typename T> inline void read(T& t)
{
    t=0; bool f=false; char ch;
    while(ch=getchar(),!((ch>='0'&&ch<='9')||ch=='-'));
    if(ch=='-') f=true,ch=getchar();
    t=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9') t=t*10+ch-'0';
    if(f) t=-t;
}
template<typename T,typename... Args> inline void read(T& t,Args&... args) { read(t); read(args...); }
const int maxn=1e6+7;
struct TwoSAT
{
    int n;
    vector<int> G[maxn*2];
    bool mark[maxn*2];
    int S[maxn*2],c;
    bool dfs(int x)
    {
        if(mark[x^1]) return false;
        if(mark[x]) return true;
        mark[x]=true;
        S[c++]=x;
        for(int i=0;i<G[x].size();i++)
            if(!dfs(G[x][i])) return false;
        return true;
    }
    //x = xval or y = yval
    void add_clause(int x,int xval,int y,int yval)
    {
        x=x*2+xval;
        y=y*2+yval;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
        //一个不满足一定能推出另一个满足
    }
    bool solve()
    {
        for(int i=0;i<n*2;i+=2)
            if(!mark[i]&&!mark[i+1])//没被访问过
            {
                c=0;
                if(!dfs(i))
                {
                    while(c>0) mark[S[--c]]=false;
                    //回溯
                    if(!dfs(i+1)) return false;
                    //true/false都不行那就无药可救了
                }
            }
        return true;
    }
}Ago;
int m,x,xval,y,yval;
int main()
{
#ifdef local
    freopen("pro.in","r",stdin);
#endif
    read(Ago.n,m);
    while(m-->0)
    {
        read(x,xval,y,yval);
        Ago.add_clause(x,xval,y,yval);
    }
    if(!Ago.solve()) printf("IMPOSSIBLE\n");
    else
    {
        printf("POSSIBLE\n");
        for(int i=1;i<=Ago.n;i++)
            if(Ago.mark[i<<1]) printf("0 ");
            else printf("1 ");
        printf("\n");
    }
    return 0;
}
posted @ 2019-08-19 20:51  happyZYM  阅读(106)  评论(0编辑  收藏  举报