蒟蒻林荫小复习——2-SAT的染色法求最小答案序列

 众所周知,2—SAT是一个很冷门的模型(冷门到连百度百科好像都没有收录),但又是一种很有用的思想,比如[NOI2017]游戏,原题是NPC问题,属于3—SAT的范畴,但是如果用一些骚操作(用DFS先确定一种车车和道路)。

话不多说,回正题(不知道什么是2-SAT模型的童鞋们建议先百度一下,学会TARJAN求2-SAT的一组解吧)。

2-SAT模型可以求出一组合法的解,而所谓最小答案序列是指选取的元素的字典序最小的答案序列。

下面我们来谈一谈染色法(这里必须要先学会TARJAN算法)。

所谓染色法分为两个部分:

1.主体(废话)

2.DFS判定函数

先放一下代码吧:

 

bool DFS(int x)
{
    if(vis[x^1])
        return false;
    if(vis[x])
    {
        return true;
    }
    vis[x]=true;
    stack[++cnt]=x;
    for(int i=0;i<b[x].size();i++)
    {
        int to=b[x][i];
        if(!DFS(to))
            return false;
    }
    return true;
}
bool SAT2(int n)
{
    for(int i=2;i<n;i+=2)
    {
        if(vis[i]||vis[i^1])
            continue;
        cnt=0;
        if(!DFS(i))
        {
            while(cnt)
            {
                vis[stack[cnt--]]=0;
            }
            if(!DFS(i^1))
                return false;
        }
    }
    return true;
}

 

代码中两个关联点的关系是靠异或连接,因此2,3代表第1个元素的两个选择,4,5代表第二个元素的两个选择

众所周知,2—SAT互相限制的实现方法是建图,如果选A必须选B,那么就从A向B拉一条有向边。

因为我们要求答案的字典序最小,所以我们对于每一个元素优先考虑第一个选择。

看代码:

1.枚举每一个元素,记该元素的两个选择为I和I^1

2.先检查I或者I^1是否因为之前的元素导致必须要选,如果是,就不用找了。

3.对I进行DFS判断,如果不能满足,消除判断过程的影响(具体影响是什么下面在DFS中解释),如果满足,直接返回1(因为I一定要小于I^1,满足字典序最小)。

4.对I^1进行DFS判断,如果仍然不合法,直接返回0(I和I^1都无法满足之前的限制条件)。

下面我们来看DFS函数是如何判断元素X是否可以满足当前2—SAT的限定条件的。

1.先看X和X^1是否已经被之前的条件强制要求

2.若两者均不,将X打上标记,塞入栈中,并且对X所要求的条件进行DFS判定,如果X所要求的条件均合法,那么X就是合法的返回1否则返回0。

至于中间的栈存的是什么:因为如果在对X所要求的条件进行判定的过程中,如果有一些条件是因为X才受到限制的(也就是说原来的这个状态单独拿出来是两个选择均可的),那么,在对X进行判定的过程中,这些点的状态均会被锁定,如果最后X没能成功满足2—SAT,选不得,那么我们需要对上述特殊点的状态进行复原(由锁定某一状态变成任意状态均可)。至于为啥不需要对DFS(X^1)同样进行复原,你怕不是傻!!!(如果X^1判定成功了就不需要复原,如果同样失败了,那整个2-SAT就说明不可能满足要求)。

重点:

这里实际上是林荫在思考中产生的一个问题,按照这个算法的思路,假设两元组(A1,A2)是一个元素的两种选择,那么按照贪心的思想,在当前能选A1的情况下一定选A1,但是如果出现了下面的情况?有形同上的二元组A,B,且A1不得与B1,B2同时出现。按照算法的思想,优先选择A1,那么会不会导致这组数据被判定无解呢?

NO!!!

因为我们首先对加边过程进行考虑,A1有向B2,B1连的边(因为A1不与B1同时出现,A1连B2,因为A1不用B2同时出现,A1连B1)。这样,在对于A1的判定中,先进入B2,给vis[B2]打上标记,然后进入B1,因为vis[B1^1]==vis[B2]==1,这样对于B1的判定会返回0,然后对于A1的判定会返回0,只能开始对A2的判定。

这样的话,基本就解决了,给个例题吧:

HDU1814

代码我直接贴了:

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
int n,m;
vector<int> b[16101];
int a1,a2;
int vis[16101],stack[16101];
int num,cnt,qlt;
bool DFS(int x)
{
    if(vis[x^1])
        return false;
    if(vis[x])
    {
        return true;
    }
    vis[x]=true;
    stack[++cnt]=x;
    for(int i=0;i<b[x].size();i++)
    {
        int to=b[x][i];
        if(!DFS(to))
            return false;
    }
    return true;
}
bool SAT2(int n)
{
    for(int i=2;i<n;i+=2)
    {
        if(vis[i]||vis[i^1])
            continue;
        cnt=0;
        if(!DFS(i))
        {
            while(cnt)
            {
                vis[stack[cnt--]]=0;
            }
            if(!DFS(i^1))
                return false;
        }
    }
    return true;
}
void LINYIN()
{
    for(int i=1;i<=n*2+2;i++)
    {
        b[i].clear();
    }
    memset(stack,0,sizeof(stack));
    memset(vis,0,sizeof(vis));
    cnt=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a1,&a2);
        a1++;
        a2++;
        b[a1].push_back(a2^1);
        b[a2].push_back(a1^1);
    }
    if(SAT2(n*2+2))
    {
        for(int i=2;i<n*2+2;i++)
        {
            if(vis[i])
                printf("%d\n",i-1);
        }
    }
    else
        printf("NIE\n");
    return ;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        LINYIN();
    }
    return 0;
}  
View Code

 

 

 完结撒花!!!

 

 

 

posted @ 2019-11-07 19:50  HA-SY林荫  阅读(310)  评论(1编辑  收藏  举报