2-SAT问题

[图论相关] [PKU 2749]Building roads 二分、构图、2-Sat验证 
[图论相关] [PKU 3683]Priest John's Busiest Day 2-Sat 
[图论相关] [PKU 3648]Wedding 2-Sat 
[图论相关] [PKU 2723]Get Luffy Out 二分、2-Sat验证 
[图论相关] [PKU 3678]Katu Puzzle 逻辑关系构图、2-Sat验证 
[图论相关] [PKU 3207]Ikki's Story IV - Panda's Trick 构图、2-sat验证
代码:

/*
参考了很多牛人的资料:伍昱的<<由对称性解2-SAT问题>>,赵爽的<<2-SAT解法浅析>>和wywcgs的有向图强连通分量代码。
特此感谢!
*/

/*
2-SAT问题:
有n个人,0,1是一组的;2,3是一组的;....;n-2,n-1是一组的。有m个限制条件,每个限制条件由a,b组成,表示a和b是不能相处的。
现在要从每组中选一个人出来构成一个团体,并且任意2个人都是能相处的。
*/

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 2100;    //最大人数

class SAT
{
    private:
        vector<int> g[N];    //原图边连接情况
        int n, m, h[N], id[N];    //id[]表示原图中每个点都属于哪个强连通分量
        int cnt, scnt, dfn[N], low[N], cur[N];
        int stack[N], top, est[N], etop;
        vector<int> tree[N];    //有向无环图的边连接情况(新图)
        vector<int> contain[N];    //新图中每个点都包含原图中的哪些点
        void dfs(int);
        void tsDfs(int);
        void topologicalSort();
        void colorDfs(int);
        void color();
        void tagAnswer();
        void printAnswer();
        void getOneAnswer();
        void buildGraph();
    public:
        void scR();
        bool build();
        bool judge();
        void solve();
};

/*
函数build是对原图初始化(根据实际输入情况做相应的更改)
*/
bool SAT::build()
{
    if (scanf("%d %d",&n,&m)==EOF)
        return false;
    for (int i=0;i<n;i++)
        g[i].clear();
    for (int i=0;i<m;i++)
    {
        int a,b,c,d;
        scanf("%d %d",&a,&b);    //a,b是互斥的。a所在组的另一点为c,b所在组的另一点为d。原图中插入边a->d和边b->c。
        if (a%2==0)
            c=a+1;
        else
            c=a-1;
        if (b%2==0)
            d=b+1;
        else
            d=b-1;
        g[a].push_back(d);
        g[b].push_back(c);
    }
    return true;
}

void SAT::dfs(int src)
{
    etop = top = 0;
    stack[top++] = src;
    while(top != 0)
    {
        int    c = stack[top-1];
        if(dfn[c] == -1)
        {
            h[c] = dfn[c] = low[c] = cnt++;
            est[etop++] = c;
        }
        for(; cur[c] >= 0; cur[c]--)
        {
            int no = g[c][cur[c]];
            if(dfn[no] == -1)
            {
                stack[top++] = no;
                break;
            }
            h[c] <?= low[no];
        }
        if(cur[c] >= 0)
            continue;
        top--;
        int k;
        if(h[c] != low[c])
        {
            low[c] = h[c];
            continue;
        }
        do
        {
            k = est[--etop];
            id[k] = scnt; low[k] = N;
        } while(k != c);
        scnt++;
    }
}

/*
函数scR和dfs是求原图的强连通分量(代码由wywcgs原创)
*/
void SAT::scR()
{
    memset(dfn, -1, sizeof(dfn));
    cnt = scnt = 0;
    for(int i = 0; i < n; i++)
    {
        cur[i] = g[i].size()-1;
        contain[i].clear();
    }
    for(int i = 0; i < n; i++)
        if(dfn[i] == -1)
            dfs(i);
    
    /*
    统计每个强连通分量都包含哪些点,为后面求可行方案做准备。如果不求可行解,可注释掉。
    */
    for (int i=0;i<n;i++)
    {
        contain[id[i]].push_back(i);
    }
}

/*
函数judge判断是否能找出一个可行方案出来
*/
bool SAT::judge()
{
    for (int i=0;i<n;i+=2)
        if (id[i]==id[i+1])
            return false;
    return true;
}

/*
函数buildGraph把每个强连通分量作为一个点,重新构图。(缩点,得到的是一个有向无环图)
用的是链接表的形式,可能有很多重边。可以加一些预处理消除重边。
*/
void SAT::buildGraph()
{
    for (int i=0;i<scnt;i++)
        tree[i].clear();
    for (int i=0;i<n;i++)
        for (int j=0;j<g[i].size();j++)
        {
            int a=id[i];
            int b=id[g[i][j]];
            if (a!=b)
            {
                tree[b].push_back(a);
            }
        }
}

void SAT::tsDfs(int k)
{
    dfn[k]=cnt++;
    for (int i=0;i<tree[k].size();i++)
    {
        int w=tree[k][i];
        if (dfn[w]==-1)
        {
            tsDfs(w);
        }
    }  
    low[scnt++]=k;            
}

/*
函数topologicalSort和tsDfs是对新图进行拓扑排序,排序后的结果存在low数组中
*/
void SAT::topologicalSort()
{
    for (int i=0;i<scnt;i++)
    {
        dfn[i]=-1;
        low[i]=-1;
    }
    int nn=scnt;
    cnt=scnt=0;
    for (int i=0;i<nn;i++)
    {
        if (dfn[i]==-1)
            tsDfs(i);
    }
}

void SAT::colorDfs(int k)
{
    dfn[k]=2;
    for (int i=0;i<tree[k].size();i++)
    {
        int w=tree[k][i];
        if (dfn[w]==-1)
        {
            colorDfs(w);
        }
    }          
}

/*
函数color和colorDfs是对新图进行着色,新图中着色为1的点组成一组可行解
*/
void SAT::color()
{
    for (int i=0;i<scnt;i++)
        dfn[i]=-1;
    for (int i=scnt-1;i>=0;i--)
        if (dfn[low[i]]==-1)
        {
            /*
            新图中low[i]着色为1后,它的矛盾点应标记为2
            */
            int a=contain[low[i]][0];    //在原图中找一点属于强连通分量low[i]的点a,点a所属组的另一点b所属的强连通分量id[b]一定是low[i]矛盾点。
            int b;
            if (a%2==0)
                b=a+1;
            else
                b=a-1;
            dfn[low[i]]=1;
            if (dfn[id[b]]==-1)
                colorDfs(id[b]);    //由于依赖关系,有id[b]能达的点都是low[i]的矛盾点
        }
}

/*
函数tagAnswer由新图对原图的点进行标记,得到原图的可行解
*/
void SAT::tagAnswer()
{
    for (int i=0;i<n;i++)
        low[i]=-1;
    for (int i=0;i<scnt;i++)
        if (dfn[i]==1)//i为新图中可行解包含的点,那么原图中强连通分量属于i的点都是原图中可行解的点
        {
            for (int j=0;j<contain[i].size();j++)
                low[contain[i][j]]=1;
        }
}

/*
函数printAnswer打印原图的可行解
*/
void SAT::printAnswer()
{
    for (int i=0,j=0;i<n;i++,j++)
        if (low[i]==1)
        {
            printf("%d",i);
            if (j!=n/2-1)
                printf(" ");
            else
                printf("/n");
        }
}

/*
函数getOneAnswer得到原图的一组可行解
*/
void SAT::getOneAnswer()
{
    buildGraph();
    topologicalSort();
    color();
    tagAnswer();
    printAnswer();
}

/*
函数solve可根据实际要求,进行更改输出
*/
void SAT::solve()
{
    scR();
    if (judge())
    {
        printf("YES/n");
        getOneAnswer();
    }
    else
        printf("NO/n");
}

int main()
{
    SAT sat;
    while (sat.build())
    {
        sat.solve();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值