2-SAT全纪录

blog里没有特别详细的讲解,所以献上dalao的讲解
不过2_SAT的模型还是很典型的:

  • 形式有点想网络流

  • 给出若干集合,集合大小一般为2

  • 一般每个集合中必选一个元素,非此即彼

  • 元素之间有一些奇怪的限制

  • 我们通过这些限制条件建图,永远记住:沿着图上的边前进代表must choose

  • 一个限制条件可能需要多条边去表示,所以记录端点和边的空间一定要计算清楚

2_SAT的精髓还是在于如何建图(简单总结一下,详细证明移步dalao的blog)

模型一:两者(A,B)不能同时取

那么选择了 A A 就只能选择B,选择了 B B 就只能选择A
连边 ABBA A → B ′ , B → A ′

模型二:两者(A,B)不能同时不取

那么选择了 A A ′ 就只能选择 B B ,选择了B就只能选择 A A
连边ABBA

模型三:两者(A,B)要么都取,要么都不取

那么选择了 A A ,就只能选择B,选择了 B B 就只能选择A,选择了 A A ′ 就只能选择 B B ′ ,选择了 B B ′ 就只能选择 A A ′
连边 ABBAABBA A → B , B → A , A ′ → B ′ , B ′ → A ′

模型四:两者(A,A’)必取A

连边 AA A ′ → A

至于2_SAT问题的求解,主要有一些几种

最简单的,判断是否有解,
我们只需要一遍tarjan缩点,保证同一集合中的元素位于不同SCC中即可证明有解
不过需要注意的是,tarjan缩点只能起到判定的作用
而且blog后面介绍的方法也只针对原图(全图)有解时才能够构造方案
对于一些最大/最小值问题,我们可以将2_SAT与二分结合

经典例题:二分+2_SAT判定

tip

tarjan一定要维护出入栈的状态

int st[N],tot=0,n;
int S[N],top;
int dfn[N],low[N],clo,cnt,belong[N];
bool in[N];

void tarjan(int now) {
    dfn[now]=low[now]=++clo;
    S[++top]=now;
    in[now]=1;                                  //入栈 
    for (int i=st[now];i;i=way[i].nxt) {
        int y=way[i].y;
        if (!dfn[y]) {
            tarjan(y);
            low[now]=min(low[now],low[y]);
        }
        else if (in[y])                         //判断是否在栈内 
            low[now]=min(low[now],dfn[y]);
    }
    if (low[now]==dfn[now]) {
        int x=-1;
        cnt++;
        while (x!=now) {
            x=S[top--];
            belong[x]=cnt;
            in[x]=0;                           //出栈 
        }
    } 
}

void init() {
    memset(belong,0,sizeof(belong));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(in,0,sizeof(in));
    clo=0; cnt=0; top=0;
}

bool solve() {
    for (int i=1;i<=2*n;i++)            //图中i与i+n是对立结点
        if (!dfn[i])
            tarjan(i);
    for (int i=1;i<=n;i++) 
        if (belong[i]==belong[i+n])
            return 0;
    return 1;
}

continue
已经确定当前图有解,如果需要输出任意一组解怎么破
注意我们再tarjan的时候求出乐意一个 belong b e l o n g 数组
记录每个结点属于哪个SCC
我们把tarjan缩点后形成的DAG重新建出来,不过需要注意:DAG中的边是反向的
在DAG上拓扑排序(染色)
遇到一个未染色的结点,我们就将其标记为选中
同时ta的对立结点标记为未选中,在DAG上沿边传递未选中标记

经典例题:构造任意解

tip

因为我们需要在对立结点上传递标记
而这个对立结点是SCC层面上的,所以我们需要记录一下每个SCC的对立SCC

struct node{
    int y,nxt;
}e[M];
int ste[N],tet=0;
int du[N],S[N],col[N],opp[N]; 

void _add(int u,int w) {
    tet++;e[tet].y=w;e[tet].nxt=ste[u];ste[u]=tet;
    du[w]++;
} 

void dfs(int now) {                                  //传递未选中标记
    if (col[now]) return;
    col[now]=-1;
    for (int i=ste[now];i;i=e[i].nxt)
        dfs(e[i].y); 
}

void TOP() {                                         //拓扑 
    int top=0;
    for (int i=1;i<=cnt;i++)                         //枚举SCC 
        if (!du[i])
            S[++top]=i;
    while (top) {
        int now=S[top--];
        if (col[now]) continue;
        col[now]=1;
        dfs(opp[now]);
        for (int i=ste[now];i;i=e[i].nxt)
        {
            du[e[i].y]--;
            if (!du[e[i].y]) S[++top]=e[i].y;
        }
    }
}

void rebuild() {
    for (int i=1;i<=2*n;i++)
        for (int j=st[i];j;j=way[j].nxt)
            if (belong[i]!=belong[way[j].y])
                _add(belong[way[i].y],belong[i]);    //反向连边建DAG

    for (int i=1;i<=n;i++) {                         //记录对立SCC 
        opp[belong[i]]=belong[i+n];
        opp[belong[i+n]]=belong[i];
    }
} 

//solve();     判定有解 

还有一部分题要求输出最小字典序解
这种情况,我们只能暴力构造解
我们对图进行染色,
首先找到编号最小的未染色的点,标记为选中,沿着边传递选中标记
如果反馈回来的信息表示染色失败(出现冲突)
我们把此次的染色信息清空,选中ta的对立结点
如果两方面染色都失败了,说明原2_SAT无解

经典例题:构造最小字典序解

tip

然而的时候一定要记录此次的染色结点
任何一个后继结点出现了矛盾,则此染色方式不可行

int S[N],top=0;
int col[N]; 

int fan(int now) {
    if (now<=n) return n+now;
    else return now-n;
}

bool print(int now) {
    if (col[now]) return 1;
    if (col[opp(now)]) return 0;
    col[now]=1;
    S[++top]=now;                              //记录染色结点 
    for (int i=st[now];i;i=way[i].nxt)
        if (!print[way[i].y]) return 0;        //任何一个后继结点出现了矛盾,返回 
    return 1;
}

bool solve() {
    memset(col,0,sizeof(col));
    for (int i=1;i<=n;i++)
        if (!col[i]&&!col[i+n])                //此集合没有染色 
        {
            top=0;
            if (!print(i)) {
                while (top) col[S[top--]]=0;
                if (!print(i+n)) return 0;
            }
        }
    return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值