zoukankan      html  css  js  c++  java
  • 2SAT学习笔记

    前置知识

    强连通分量

    例题引入

    先来看一个问题:
    给定\(n\)个元素,分别是\(a_1,a_2,...,a_n\)
    每个\(a_i\)只有\(0\)\(1\)两种取值,再给定\(m\)个约束条件,
    条件的形式都是 "若\(a_x\)\(p\),则\(a_y\)\(q\)" 其中\(p,q\) \(\in\) \(\{ {0,1} \}\)
    请问是否有一组合法的\(a\)的取值满足以上条件

    这就是\(2-SAT\)问题的经典模型。
    \(2-SAT\)中有个十分重要的结论:

    • \(A\)\(B\);可以推出:若非\(B\),则非\(A\)
      即每个命题的逆否命题一定成立

    例如:若\(a_1\)\(1\),则\(a_2\)\(0\)
    那么:若\(a_2\)\(1\)(不为\(0\)),则\(a_1\)\(0\)(不为\(1\))。

    所以得到上述例题的做法:

    1. 编号\(i\)代表\(a_i\)\(0\),\(i+n\)代表\(a_i\)\(1\)
    2. 对于若\(a_x\)\(p\),则\(a_y\)\(q\),那么让\(x+p*n\)\(y+q*n\)连边。
      那么我们得出\(a_y\)不为 \(q\),则\(a_x\)不为 \(p\)(逆否命题),那么让\(y+(\)~\(q) \times n\)\(x+(\)~\(p) \times n\)连边。 ( \(~\) 代表取反)
    3. 求强连通分量,记录每个节点属于哪一个强连通分量。
    4. 很显然,属于同一个强连通分量的值都相同,\(i\)不可能与\(i+n\)处于同一个强连通分量
      假如我们求出来\(c\)数组代表每个节点属于哪个强连通分量,
      如果对于任意的\(c_i\)\(c_{i+n}\)都不相同,那么\(2-SAT\)问题有解,否则无解。

    时间复杂度\(O(V+E)\) (\(V\)\(E\)是建完图后的点数和边数)

    性质

    通过上述例题,我们发现,因为最先提出的两个命题是成对出现的,
    所以我们建出来的图也是具有对称性的,所以建边时必须注意这个性质。
    值得一提的是,如果一个元素的值是确定的,那么我们让\(i+n\)\(i\)\(i\)\(i+n\)连边,以便直接产生矛盾。

    如何求出一组解

    大部分\(2-SAT\)题目都需要输出一组可行解,其中有两种\(2-SAT\)问题的构造方法。
    第一种:
    因为同一\(SCC\)中的元素值相同,所以其中一个元素的值确定,其他元素的值也是相同的。
    我们考虑缩点,把每个\(SCC\)看成一个点,缩点后的图就是一个\(DAG\)
    接下来用拓扑排序遍历整个图。
    每次我们需要找出没有出度的点,防止对其他点产生影响。
    而通常拓扑排序都是寻找没有入度的点,所以我们需要对原图的反图进行拓扑排序。
    代码大致实现:

    void topsort(){
    	queue<int>q;
    	memset(now,tot=0,sizeof(now));
    	//建反图 
    	for(int i=1;i<=Ed;i++){
    		int X=E[i].x,Y=E[i].y;
    		if(X!=Y)add(Y,X),deg[X]++;
    	}
    	//cnt:SCC的个数 
    	for(int i=1;i<=cnt;i++)
    		if(!deg[i])q.push(i);
    	//拓扑排序 
    	while(q.size()){
    		int u=q.front();q.pop();
    		//检查是否已经被赋值
    		//opp[u] : 第u个SSC 与它不同的 SSC 
    		if(!val[u]){
    			val[u]=1;
    			val[opp[u]]=2;
    		} 
    		for(int i=now[u];i;i=pre[i])
    			if(!--deg[to[i]])q.push(v);
    	}
    	//输出一组解 
    	for(int i=1;i<=n;i++)
    		if(val[c[i]]==1)printf("1 ");
    		else printf("0 ");
    }
    

    第二种:
    事实上,\(tarjan\)算法求出来的每个\(SCC\)的编号就是缩点后图的拓扑序。
    我们可以直接利用\(SCC\)编号的大小关系确定元素的值,这使得过程非常简单。
    代码:

    for(int i=1;i<=n;i++)
        if(c[i]<c[i+n])printf("1 ");
        else printf("0 ");
    

    习题

    模板

    尝试转换成\(2-SAT\)的限制条件。
    很显然,\(x\)\(p\)\(y\)\(q\) \(~~~\) 转换成 \(~~~\) \(y\)不为\(q\)\(x\)\(p\)

    [POI2001]Peaceful Commission

    模板

    poj3678 Katu Puzzle

    对于六种情况分别讨论,
    特别的,如果是\(a\) \(and\) \(b\) \(=\) \(1\)\(a\) \(or\) \(b\) = \(0\),那么\(a,b\)的值是确定的。
    那么需要让\(a+n\)\(a\)\(a\)\(a+n\)连边,\(b+n\)\(b\)\(b\)\(b+n\)连边。

    poj3683 Priest John's Busiset Day

    通过时间是否重叠建图

    poj3648Weeding

    这题有两种方法:
    第一种是用\(0\)\(1\)代表这个位置是\(h\)还是\(w\)
    第二种是让每个人都有两个取值\(0\)\(1\),表示坐新郎这边还是坐新娘那边。

    NOI2017游戏

    除去\(x\)类图,就是最简单的\(2-SAT\)
    鉴于\(d \leqslant 8\)我们想到直接暴力枚举\(x\);
    \(O(3^d)\)枚举每个 \(x\)类型仍然会超时。
    我们可以\(O(2^d)\)枚举不适合哪两种车,那么三种车都能包含到。

    模板

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e6+10;
    int n,m,cnt;
    int pre[N],now[N],to[N],tot;
    int sk[N],top;
    int low[N],dfn[N],num,c[N];
    bool vis[N];
    void add(int x,int y){
    	pre[++tot]=now[x];
    	now[x]=tot;to[tot]=y;
    }
    void tarjan(int u){
    	low[u]=dfn[u]=++num;
    	vis[sk[++top]=u]=true;
    	for(int i=now[u];i;i=pre[i]){
    		int v=to[i];
    		if(!dfn[v]){
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(vis[v])
    			low[u]=min(low[u],dfn[v]);
    	}
    	if(dfn[u]==low[u]){
    		int v;cnt++;
    		do{
    			v=sk[top--];
    			vis[v]=false;
    			c[v]=cnt;
    		}while(u!=v);
    	}return;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	while(m--){
    		int i,j,a,b;
    		scanf("%d%d%d%d",&i,&a,&j,&b);
    		if(a&&b)add(i+n,j),add(j+n,i);
    		if(!a&&!b)add(i,j+n),add(j,i+n);
    		if(!a&&b)add(i,j),add(j+n,i+n);
    		if(a&&!b)add(i+n,j+n),add(j,i);
    	}
    	for(int i=1;i<=2*n;i++)
    		if(!dfn[i])tarjan(i);
    	for(int i=1;i<=n;i++){
    		if(c[i]==c[i+n]){
    			puts("IMPOSSIBLE");
    			return 0;
    		}
    	}
    	puts("POSSIBLE");
    	for(int i=1;i<=n;i++)
    		printf("%d ",(c[i]<c[i+n])?1:0);
    	return 0;
    }
    
  • 相关阅读:
    cat
    cal
    API、ABI区别
    html 实体转换为字符:转换 UEditor 编辑器 ( 在 ThinkPHP 3.2.2 中 ) 保存的数据
    IDEA突然无法运行
    Java实现 蓝桥杯 算法提高 成绩排名
    Java实现 蓝桥杯 算法提高 成绩排名
    Java实现 蓝桥杯 算法提高 成绩排名
    Java实现 蓝桥杯 算法提高 Monday-Saturday质因子
    Java实现 蓝桥杯 算法提高 Monday-Saturday质因子
  • 原文地址:https://www.cnblogs.com/Xxhdjr/p/14426354.html
Copyright © 2011-2022 走看看