码迷,mamicode.com
首页 > 其他好文 > 详细

「LG4782 模板 2-SAT 问题」

时间:2019-02-26 17:47:50      阅读:130      评论:0      收藏:0      [点我收藏+]

标签:||   输出   选择   不难   mat   get   情况   show   编号   

题目

来学\(2\)-\(sat\)

这个东西确实不难

这个算法就是给你一堆\(bool\)变量\(x_1,x_2...x_n\),之后给你一些限制

限制的形式就是给你一对\((u,o1,v,o2)\)

\(x_u=o1\)或者\(x_v=o2\)

之后满足所有限制

这个东西非常容易就能抽象成一个图论模型

我们把每个\(x_i\)拆成\(i\)\(i'\)两个点,分别表示真和假

我们对于每一个限制,连出去一些边,边\((x,y)\)的含义是选择了\(x\)就必须选择\(y\)

举个例子吧

如果一条限制是

\[(u,0,v,1)\]

那么\(u\)\(v\)连边,表示\(x_u=1\)的时候\(x_v\)也得等于\(1\)

同时\(v'\)\(u'\)连边,表示\(x_v=0\)的时候\(x_u\)只能等于\(0\)

这样的话如果从\(i\)能通过这种边一路推到\(i'\)就表示让\(x_i=1\)的话就必须让\(x_i=0\)这显然不合理

于是就能判断无解情况了,一个\(tarjan\)下去找一个强联通分量

还有些题目需要我们构造出一组可行解

其实就是输出\(col[i]<col[i']\)

\(col[i]\)就是这个点所属强联通分量的编号

看起来很神奇,其实是这样的

我们考虑把\(tarjan\)缩完点的图建出来,发现我们肯定是尽量优先选择那些拓扑序较大的,因为优先选择拓扑序较大的可以使得必须选择的点少一些

由于\(tarjan\)的性质\(col\)正好是反向拓扑序,于是如果\(i\)\(col\)较小,也就是拓扑序较大,那就优先选择\(x_i=1\)

没了

#include<cstdio>
#include<cstring>
#include<iostream>
#define min(a,b) ((a)<(b)?(a):(b))
#define re register
#define maxn 2000005
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
struct E{int v,nxt;}e[maxn];
int n,num,cnt,p,mid,top,m;
int col[maxn],head[maxn],dfn[maxn],low[maxn],st[maxn],f[maxn];
inline void add(int x,int y) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;}
void tarjan(int x) {
    dfn[x]=low[x]=++cnt;st[++top]=x;f[x]=1;
    for(re int i=head[x];i;i=e[i].nxt) 
    if(!dfn[e[i].v]) tarjan(e[i].v),low[x]=min(low[x],low[e[i].v]);
        else if(f[e[i].v]) low[x]=min(low[x],dfn[e[i].v]);
    if(low[x]!=dfn[x]) return;++p;
    do{mid=st[top--];f[mid]=0,col[mid]=p;}while(mid!=x);
}
int main() {
    n=read(),m=read();
    int x,y,o1,o2;
    for(re int i=1;i<=m;i++) {
        x=read(),o1=read(),y=read(),o2=read();
        if(o1==1) {if(!o2) add(x+n,y+n);else add(x+n,y);}
        if(o1==0) {if(!o2) add(x,y+n);else add(x,y);}
        std::swap(x,y),std::swap(o1,o2);
        if(o1==1) {if(!o2) add(x+n,y+n);else add(x+n,y);}
        if(o1==0) {if(!o2) add(x,y+n);else add(x,y);}
    }
    for(re int i=1;i<=n+n;i++) if(!dfn[i]) tarjan(i);
    for(re int i=1;i<=n;i++) if(col[i]==col[i+n]) {puts("IMPOSSIBLE");return 0;}
    puts("POSSIBLE");
    for(re int i=1;i<=n;i++) printf("%d ",col[i]<col[i+n]);
    return 0;
}

「LG4782 模板 2-SAT 问题」

标签:||   输出   选择   不难   mat   get   情况   show   编号   

原文地址:https://www.cnblogs.com/asuldb/p/10438324.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!