zoukankan      html  css  js  c++  java
  • 【瞎口胡】2-SAT 问题

    Luogu P4782

    (n) 个变量 (x_1 sim x_n(x_i in {0,1})),另有 (m) 个需要满足的条件,每个条件的形式都是 「(x_i)(1) / (0)(x_j)(0) / (1)」。比如 「(x_1)(0)(x_3)(1)」、「(x_7)(0)(x_2)(0)」。

    2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

    (n,m leq 10^6)

    上述问题即为一个 2-SAT 问题。这类问题通常是给只有两种取值的变量赋值,给出限制(但种类不仅仅是上题中出现的),要求给出一组合法解。

    解决 2-SAT 问题一般转化为图论模型,然后使用图论相关算法求解。

    我们考虑把每一个限制转化为若干条边 (i o j),含义是 「选择 (i) 就必须选 (j)」。

    因为每个变量有 (0)(1) 两种选择,所以我们将每个变量 (i) 表示为两个图上的点 (i_0,i_1)。其中 (i_0) 表示 (i)(0)(i_1) 表示 (i)(1)

    考虑如何将限制转化为图上的若干条边。

    • 一元限制:强制变量 (i)(0)(1)

      (i)(0) 举例,则我们需要建边 (i_0 o i_1),含义是 「选择了 (i=0) 就必须选择 (i=1)」,即强制使 (i)(1)

    • 变量 (i) 和变量 (j) 同时为 (0)(1)(i~ ext{or}~j=0)(i ~ ext{and}~j=1)

      那么 (i,j) 都为 (0) / (1)。即建立两个一元限制。

    • 变量 (i)(1) 或变量 (j)(1)(i~ ext{or}~j=1)

      (i)(0) 时,(j) 必须为 (1),建边 (i_0 o j_1)

      (j)(0) 时,(i) 必须为 (1),建边 (j_0 o i_1)

    • 变量 (i)(0) 或变量 (j)(0)(i~ ext{and}~j=0)

      跟上个限制类似,建边 (i_1 o j_0)(j_1 o i_0)

    • 变量 (i) 和变量 (j) 相同((i=j)

      注意:这里一共要建 (4) 条边。

      (i)(0) 时,(j) 必须为 (0),建边 (i_0 o j_0)

      剩余的边类似,(i_1 o j_1,j_0 o i_0,j_1 o i_1)

    • 变量 (i) 和变量 (j) 不同((i eq j)

      (i_0 o j_1,i_1 o j_0,j_0 o i_1,j_1 o i_0)

    我们观察到,如果在图上 (i_0)(i_1) 互相可达(即存在从 $i_0 $ 到 (i_1) 的路径,也存在从 (i_1)(i_0) 的路径),那么此问题无解,因为每个变量不可能同时取多个值。

    又观察到如果互相可达,则它们在一个强连通分量中。所以求出强连通分量,对于每个 (i_0,i_1) 依次判断即可知道问题是否有可行解。

    如果题目要求构造可行解,则将原图缩点成 DAG 后拓扑排序。如果 (i_0) 所在强连通分量的拓扑序比 (i_1) 小,则 DAG 上可能会存在一条 (i_0 o ... o i_1) 的路径。此时如果将 (i) 赋值为 (0),那么从 (i_0) 沿着路径往前走到 (i_1),会发现 (i) 的值必须为 (1),此时矛盾,所以应该将 (i) 赋值为 (1)

    总而言之,如果 (i_x) 的拓扑序小于 (i_{x~ ext{xor}~1}),应该将 (i) 赋值为 (x~ ext{xor}~1)

    小技巧:如果使用 Tarjan 算法,则先找到的强连通分量,在新 DAG 中的拓扑序靠后。在代码实现中将找到的强连通分量依次标号 (col_x),那么当 (col_x < col_y) 时,(x) 的拓扑序比 (y) 大。

    回到模板题。此题中的限制都是 (i~ ext{or}~j=1) 类型的,按照上面的方法建边即可。

    # include <bits/stdc++.h>
    # define rr register
    const int N=2000010;
    struct Edge{
    	int to,next;
    }edge[N<<2];
    int head[N],sum;
    std::stack <int> k;
    int n,m;
    bool vis[N];
    int dfn[N],low[N],dfncount,scccount;
    int col[N];
    inline int read(void){
    	int res,f=1;
    	char c;
    	while((c=getchar())<'0'||c>'9')
    		if(c=='-')f=-1;
    	res=c-48;
    	while((c=getchar())>='0'&&c<='9')
    		res=res*10+c-48;
    	return res*f;
    }
    inline void add(int x,int y){
    	edge[++sum].to=y;
    	edge[sum].next=head[x];
    	head[x]=sum;
    	return;
    }
    void tarjan(int i){
    	low[i]=dfn[i]=++dfncount;
    	vis[i]=true;
    	k.push(i);
    	for(rr int j=head[i];j;j=edge[j].next){
    		int to=edge[j].to;
    		if(!vis[to]&&dfn[to]){
    			continue;
    		}
    		if(!dfn[to]){
    			tarjan(to);
    		}
    		low[i]=std::min(low[i],low[to]);
    	}
    	if(low[i]==dfn[i]){
    		++scccount;
    		while(k.top()!=i){
    			int u=k.top();
    			vis[u]=false,col[u]=scccount,k.pop();
    		}
    		vis[i]=false,col[i]=scccount,k.pop();
    	}
    	return;
    }
    inline int id(int a,int b){
    	return a+n*b;
    }
    int main(void){
    	n=read(),m=read();
    	for(rr int i=1;i<=m;++i){
    		int u=read(),a=read(),v=read(),b=read();
    		add(id(u,a^1),id(v,b));
    		add(id(v,b^1),id(u,a));
    	}
    	for(rr int i=1;i<=n*2;++i){
    		if(!dfn[i]){
    			tarjan(i);
    		}
    	}
    	for(rr int i=1;i<=n;++i){
    		if(col[id(i,0)]==col[id(i,1)]){
    			printf("IMPOSSIBLE");
    			return 0;
    		}
    	}
    	puts("POSSIBLE");
    	for(rr int i=1;i<=n;++i){
    		printf("%d ",(col[id(i,1)]<col[id(i,0)]));
    	}
    	return 0;
    }
    
  • 相关阅读:
    值类型和引用类型
    0513二分查找练习
    0512随机4位验证码
    0511java 随机6个不同的彩票数
    随机数的产生机制
    0510Java 练习
    0509java练习题
    java循环作业
    字符集的由来及发展
    hdu2577_键入字母
  • 原文地址:https://www.cnblogs.com/liuzongxin/p/13729912.html
Copyright © 2011-2022 走看看