• 题解「Luogu4782 【模板】2-SAT 问题」


    SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 (k>2) 时该问题为 NP 完全的。所以我们只研究 (k=2) 的情况。

    ——摘自 OI-Wiki

    2-SAT问题大多是固定的模型:

    给定若干个均有两个元素的集合,为了方便,我们称集合 (i) 中一个元素为 (A_i) ,另一个元素为 (A_i') 。现在给出若干限制条件,如选择 (A_i) 就不能选择 (A_j) ,选择 (A_i) 就必须选择 (A_j) 等(其中 (i ot= j) )。询问是否存在一种方案,能够在满足限制条件的同时从每一个集合中都选出一个元素。

    发现一个限制条件只和两个集合有关,于是我们可以考虑将这种关系抽象成图上的边,将集合中对立的两个元素抽象成点。

    若现在有这样一个限制:选择 (A_i) 就不能选择 (A_j) 。那么如果选择 (A_i) 就必须选择 (A_j') ,选择 (A_j) 就必须选择 (A_i') 。对于这样的限制,连边 (A_i o A_j')(A_j o A_i') 。于是图中的边有了具体意义:(u o v) 的边表示若选 (u) 则必选 (v)

    不难发现,若一个点 (u) 被选择,则 (u) 所能到达的点均必须被选择。这个性质明显与连通性相关。

    于是有了一个结论:若一个点 (u) 被选择,那么与 (u) 在同一个强连通分量中的点均必须被选择。

    这句话说明同一个强连通分量中的点取或不取的状态是一致的

    这样就能判断是否有解了:若同一个集合中的两个元素在同一个强连通分量中,则问题无解。很显然,若 (A_i)(A_i') 要么同时选,要么都不选,则不满足题设。

    求出强连通分量后判断无解:

    for(int i=1;i<=n;++i)
    	if(scc[i]==scc[i+n])
    	{
    		puts("IMPOSSIBLE");
    		return 0;
        }
    

    但是有些题不止让你判断有无解,还让你输出可行解,这就需要用到拓扑排序

    (S_i) 表示 (A_i) 所在的强连通分量,(S_i') 表示 (A_i') 所在的强连通分量。

    推广一下上面对于单点的结论:若一个强连通分量 (S) 被选择,则 (S) 所能到达的强连通分量均必须被选择

    先考虑这样一种情况: (S_i)(S_i') 联通,并且假设 (S_i) 的拓扑序小于 (S_i') 的拓扑序。则必然有:若 (S_i) 被选择,则 (S_i') 必须被选择。那肯定不能选 (S_i) 了,因为 (S_i)(S_i') 必然不能同时选。这样我们就确定了选择方案。

    (S_i)(S_i') 不连通的话,上面的方法就不行了,因为没法确定选择方案,而一个看起来满足要求的方案可能会对其他的强连通分量产生影响,而枚举方案肯定复杂度爆炸。

    我们尝试将两种情况进行统一,即选择 (S_i)(S_i') 中拓扑序大的那个强连通分量。而这样选择其实是正确的,来简单证明一下:

    由最初的连边方式可知,得出的图是对称的:

    aqViHP.png

    所以缩点后的DAG也是对称的:

    aqVNv9.png

    考虑这么一个DAG:

    aqVWDI.png

    不难看出 (S_i') 的后继结点与 (S_i) 的前驱结点是对称的。

    于是选择 (S_i') 会导致 (S_i') 的后继结点全部被选择,而选择 (S_i') 就必须不选 (S_i) ,选择 (S_i') 的后继结点会导致 (S_i) 的前驱结点不被选择。也就是说,(S_i') 以下的结点全选, (S_i) 以上的结点全不选。不难看出,这样选择对虚线框中的结点无影响。

    于是可知,这样选择不会导致矛盾。


    跑完tarjan后不需要再拓扑,因为tarjan求出的scc编号就是逆拓扑序

    注意一下:若将 (i) 集合拆成 i<<1i<<1|1 两个点,那么从2扫描到n<<1|1 求出的拓扑序对于第二种情况会出锅,大概是因为同一个连通块中结点的拓扑序不连续造成的。


    ( ext{Code}:)

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <stack>
    #define maxn 2000005
    #define R register
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long lxl;
    
    inline int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    struct edge
    {
    	int u,v,next;
    }e[maxn];
    
    int head[maxn],k;
    
    inline void add(int u,int v)
    {
    	e[k]=(edge){u,v,head[u]};
    	head[u]=k++;
    }
    
    int n,m,ans[maxn];
    int dfn[maxn],low[maxn],dfs_cnt,scc[maxn],scc_cnt;
    std::stack<int> S;
    bool vis[maxn];
    
    inline void tarjan(int u)
    {
    	S.push(u);
    	dfn[u]=low[u]=++dfs_cnt;
    	for(int i=head[u];~i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(!dfn[v])
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(!scc[v]) low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		++scc_cnt;
    		int x;
    		do
    		{
    			x=S.top();S.pop();
    			scc[x]=scc_cnt;
    		} while (x!=u);
    	}
    }
    
    int main()
    {
    	// freopen("P4782.in","r",stdin);
    	n=read(),m=read();
    	memset(head,-1,sizeof(head));
    	for(int i=1;i<=m;++i)
    	{
    		int x=read(),a=read(),y=read(),b=read();
    		add(x+n*(a&1),y+n*(b^1));
    		add(y+n*(b&1),x+n*(a^1));
    	}
    	for(int i=1;i<=(n<<1);++i)
    		if(!dfn[i]) tarjan(i);
    	for(int i=1;i<=n;++i)
    		if(scc[i]==scc[i+n])
    		{
    			puts("IMPOSSIBLE");
    			return 0;
    		}
    	puts("POSSIBLE");
    	for(int i=1;i<=n;++i)
    		printf("%d ",scc[i]<scc[i+n]);
    	return 0;
    }
    

    参考文献:国家集训队2003年论文集:伍昱--由对称性解2-SAT问题

  • 相关阅读:
    JAVA中SpringMVC获取bean方法,在工具类(utils)注入service
    JAVA中json对象转JAVA对象,JSON数组(JSONArray)转集合(List)
    maven中pom文件中scope的作用
    页面图片懒加载、延迟加载(lazyload)
    js(jQuery)获取自定义data属性的值
    ubantu使用小结
    ipmitool管理工具
    大于2T的硬盘怎么分区
    lamp的动静分离部署
    keepalived脑裂问题
  • 原文地址:https://www.cnblogs.com/syc233/p/13473399.html
Copyright © 2020-2023  润新知