• 2-SAT


    简介

    2-SAT是一类适定性问题

    适定性问题?

    通俗的说就是确定是否可以满足所有的条件

    k-SAT

    有很多个集合,每个集合里面有若干元素,现给出一些取元素的规则,要你判断是否可行,可行则给出一个可行方案。如果所有集合中,元素个数最多的集合有k个,那么我们就说这是一个k-sat问题
    3-SAT乃至k更大的情况已经被证明为是 NP完全问题了

    2-SAT

    在更多的情况下,2-SAT不仅仅是元素个数最多的集合含有2个元素,而是每个集合都含有2个元素,且这2个元素不允许同时取出
    我们主要研究这一类问题


    解决

    例题---Poi2001 和平委员会

    Description

    根据宪法,Byteland民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。
    此委员会必须满足下列条件:
    每个党派都在委员会中恰有1个代表,
    如果2个代表彼此厌恶,则他们不能都属于委员会。
    每个党在议会中有2个代表。代表从1编号到2n。 编号为2i-1和2i的代表属于第I个党派。
    任务
    写一程序:
    输入党派的数量和关系不友好的代表对,
    计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。

    Input

    第一个行有2非负整数n和m。 他们各自表示:党派的数量n,1<=n<=8000和不友好的代表对m,0 <=m <=20000。 在下面m行的每行为一对整数a,b,1<=a

    Output

    如果委员会不能创立,输出中应该包括单词NIE。若能够成立,输出中应该包括n个从区间1到2n选出的整数,按升序写出,每行一个,这些数字为委员会中代表的编号。 如果委员会能以多种方法形成,程序输出字典序最小的一个。

    Sample Input

    3 2
    1 3
    2 4

    Sample Output

    1
    4
    5

    分析

    这是典型的2-SAT题
    可以去这里交(多组数据)

    算法1

    设党派A, B:A中两个人A1, A2;B中两个人B1, B2
    如果A1, B2有矛盾,那么A1与B1连单向边,表示选了A1就只能选B1
    要求字典序最小,从小到大枚举人,Dfs染色,有冲突时撤销
    没错,就是这么暴力

    # include <bits/stdc++.h>
    # define RG register
    # define IL inline
    # define Fill(a, b) memset(a, b, sizeof(a))
    using namespace std;
    typedef long long ll;
    const int _(2e4 + 5);
    
    IL int Input(){
        RG int x = 0, z = 1; RG char c = getchar();
        for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
        for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
        return x * z;
    }
    
    int n, m, first[_], cnt, col[_], num, S[_];
    struct Edge{
    	int to, next;
    } edge[_ << 1];
    
    IL void Add(RG int u, RG int v){
    	edge[cnt] = (Edge){v, first[u]}; first[u] = cnt++;
    }
    
    IL int Oth(RG int x){
    	return (x & 1) ? x + 1 : x - 1;
    }
    
    IL bool Dfs(RG int u){
    	S[++S[0]] = u;
    	if(col[u] == col[Oth(u)]) return 0;
    	for(RG int e = first[u]; e != -1; e = edge[e].next){
    		RG int v = edge[e].to;
    		if(!col[v]){
    			col[v] = col[u];
    			if(!Dfs(v)) return 0;
    		}
    		else if(col[v] != col[u]) return 0;
    	}
    	return 1;
    }
    
    int main(RG int argc, RG char* argv[]){
    	while(scanf("%d%d", &n, &m) != EOF){
    		Fill(first, -1), Fill(col, 0), num = cnt = 0;
    		for(RG int i = 1; i <= m; ++i){
    			RG int u = Input(), v = Input();
    			Add(u, Oth(v)), Add(v, Oth(u));
    		}
    		RG int tmp = n << 1;
    		for(RG int i = 1; i <= tmp; ++i){
    			if(col[i]) continue;
    			col[i] = 2, S[0] = 0;
    			if(Dfs(i)) continue;
    			for(RG int j = 1; j <= S[0]; ++j) col[S[j]] = 0;
    		}
    		for(RG int i = 1; i <= tmp; ++i) num += col[i] == 2;
    		if(num != n) puts("NIE");
    		else for(RG int i = 1; i <= tmp; ++i) if(col[i] == 2) printf("%d
    ", i);
    	}
    	return 0;
    }
    

    算法2

    建立于不用字典序最小的情况下一个优美的优化
    还是直接看别人的博客

    连边

    拆点a,a',表示选或不选
    a->b表示选a必须选b

    • a、b不能同时选:选了a就要选b',选了b就要选a'
    • a、b必须同时选:选了a就要选b,选了b就要选a,选了a'就要选b',选了b'就要选a'
    • a、b必须选一个:选了a就要选b',选了b就要选a',选了a'就要选b,选了b'就要选a
    • a必须选:a'->a。

    流程

    • 连边
    • 跑tarjan
    • 判可行性,即同一集合中的两个点是否同属一个强连通分量
    • 缩点建新图,边反向
    • 记录矛盾,一个集合的两个元素分布在了两个不同的块中,那么这两个块就是矛盾的,即不可能同时被选择,,我们要找到与一个块对立的块,并把它们保存下来。
    • 拓扑排序,若当前点没有被访问过,则选择该点,不选择其另外的点

    任意一组可行解拓扑排序求出
    做法:
    选A就必须选B等价于不选B就必须不选A
    也就是说把边反向后,选了A,那么A'就不能选,染色就好了,注意A'不能选,则这个反边的图中A'相连的都不能选,要传递下去
    详见Poj3863
    问题就解决了

    题目

    POJ 3678
    POJ 2723
    POJ 2749
    POJ 3863

  • 相关阅读:
    struts2实现文件上传和下载
    Struts2中Action之ResultType
    初识Struts2
    Hibernate中get()和load()方法区别
    初识Hibernate框架,进行简单的增删改查操作
    memge和saveOrUpdate的区别
    apt-get install 出现could not open lock file /var/lib/dpkg/lock错误问题
    vscode工程目录文件及文件夹摘选
    C++引用
    内存分配区基本模型
  • 原文地址:https://www.cnblogs.com/cjoieryl/p/8460181.html
Copyright © 2020-2023  润新知