PS:今天(2014.10.27)准备PPT,明天在组合数学课上与大家一起分享一下2-SAT。我以为是一件简单的事情。但是,当我看了自己这篇博客以后,发现居然还是不懂。很多资料不全,也没仔细讲。整理了一下,又修改了。尽量让自己下次再看到这博客的时候,一遍下来就能懂。虽然引用了别人的博客,我尽量讲得不需要进入别人的博客就能看懂。

  一些这方面的内容写得很好的博客。(排列是随机的。可以略过了)

  http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html  解释很清晰,但是代码风格不是我想要的。

  http://blog.csdn.net/hqd_acm/article/details/5881655  内容很详细,方法也很好,不过没有代码是个致命缺憾。

定义:

  如果存在一个真值分配,使得布尔表达式的取值为真,则这个布尔表达式称为可适定的,简称SAT。

  例如(x1+x2)(┐x1+┐x2)是一个布尔表达式,如果p(x1)=“真”,p(x1)=“假”,则表达式的值为真,则这个表达式是适定的。上面的“+”是逻辑或。不是所有的布尔表达式都是可适定的,必须存在使得表达式为真的情况。

2-SAT:设X={x1,x2,…,xn,┐x1,┐x2,…,┐xn},布尔表达式中只有两种形式,一种是单个布尔变量a属于X,另一种是两个变量a,b的或(a+b),a,b都属于X。

   例如:(a+b)·(c+d)·e ,这是一个合取范式,每一个布尔表达式,如(a+b)最多只有两个元素。k-SAT,当k>2时,问题为完全的。

   2-SAT问题就是求使表达式为真的解。

  学习2-SAT必须先掌握强连通和拓扑排序。

2-SAT有三种题型,不同题型的解题方法不同。

  第一种,暴力求最小字典序解。复杂度O(n*m)。

  第二种,强连通判断有没有解。O(n+m)。

  第三种,强连通+缩点+拓扑排序,求任意解。O(n+m)(第三种会了,第二种也就会了,严格说来,第二种应该和第三种合并成第二种)

  虽然题型不同,但是建图的方式还是相同的。边u->v的含义是选择了u ,就必须选择v。

  

  第一种类型,暴力求最小字典序解。

  典型题目 hdu1814 Peaceful Commission

  题目大意:

   根据宪法,Byteland民主共和国的公众和平委员会应该在国会中通过立法程序来创立。

    不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。
  此委员会必须满足下列条件:
    1、每个党派都在委员会中恰有1个代表,
    2、如果2个代表彼此厌恶,则他们不能都属于委员会。
    3、每个党在议会中有2个代表。代表从1编号到2n。 编号为2i-1和2i的代表属于第I个党派。
  计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。求出字典序最小的解
题目分析:
 此题麻烦之处在于要求出字典序最小的解。所以才用暴力枚举来做,也就是一个一个试过去。找到解就是最小的字典序解。

  我们给结点染色,假设白色是未染色,红色是要选取的结点,蓝色是抛弃的结点。
  首先从第一个点开始染色,染成红色,同时将同组另一节点染成蓝色,然后将这个点的所有后继结点也染成红色,同时开一个数组记录都染了哪些结点。如果后来发现某结点的后继是蓝色,说明本次染色失败,因为碰到了矛盾(假如一个结点被选取,那么所有后继肯定也得全部选取)。那么因为刚才染色的时候记录了染色的结点,靠这个数组将结点全部还原回白色。然后从第二个结点开始探索,直到全部染色完毕,最后的红色结点就是答案。

  这种方法的时间复杂度为O(m*n)。m为边数,n为顶点数。

此题最好的题解(我认为)是http://blog.163.com/shengrui_step/blog/static/20870918720141201262750/  染色,很容易懂
下面是我的代码:vis[]标记选择,-1为没有标记,1是标记为选择,0为放弃。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=,M=;
int vis[N],head[N],ch[N];
int tot,cnt;
struct node
{
int to,next;
}edge[M];
void addedge(int i,int j)
{
edge[tot].to=j;edge[tot].next=head[i];head[i]=tot++;
}
int dfs(int u)
{
if(vis[u]==) return ;
if(vis[u]==) return ;
vis[u]=;
vis[u^]=;
ch[cnt++]=u;
for(int k=head[u];k!=-;k=edge[k].next)
if(!dfs(edge[k].to)) return ;
return ;
}
int sat2(int n)
{
for(int i=;i<*n;i++)
{
if(vis[i]==-)
{
cnt=;
if(!dfs(i))
{
for(int j=;j<cnt;j++)
vis[ch[j]]=vis[ch[j]^]=-;
if(!dfs(i^)) return ;
}
}
}
return ;
}
void init()
{
tot=;
memset(head,-,sizeof(head));
memset(vis,-,sizeof(vis));
}
int main()
{
//freopen("test.txt","r",stdin);
int n,m,i,j,k;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
while(m--)
{
scanf("%d%d",&i,&j);
i--;j--;
addedge(i,j^);
addedge(j,i^);
}
if(sat2(n))
{
for(i=;i<*n;i++)
if(vis[i]==)printf("%d\n",i+);
}
else printf("NIE\n");
}
return ;
}

第二种类型,强连通求可行解。
典型题目 hdu3622 Bomb Game
参照http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712424.html 不过,要说一说,强连通的三个模版中,二次搜索的那个很弱的。
题目大意:
  给n对炸弹可以放置的位置(每个位置为一个二维平面上的点),每次放置炸弹是时只能选择这一对中的其中一个点,每个炸弹爆炸的范围半径都一样,控制爆炸的半径使得所有爆炸范围都不相交(可以相切),求解这个最大半径。
分析:
  求强连通的目的是什么?属于一个强连通的点是必须一起选择的。如果一对关系点(只能且必须选其中一个)属于同一个强连通,就产生了矛盾,就无解。这是这种题型的解题思想。
  这题多了二分查找,显得麻烦。但是二分技术在最大流和二分匹配中都有的。懂了就好了。
时间复杂度是O(m),主要是求强连通分量。
下面的我的代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std; const int N =, M=;
const double eps=10e-;
struct node
{
int to, next;
}edge[M];
int head[N], low[N], dfn[N], sta[N], belg[N], num[N];
bool vis[N];
int scc,index,top, tot;
void tarbfs(int u)
{
int i,j,k,v;
low[u]=dfn[u]=++index;
sta[top++]=u;
vis[u]=;
for(i=head[u];i!=-;i=edge[i].next)
{
v=edge[i].to;
if(!dfn[v])
{
tarbfs(v);
if(low[u]>low[v]) low[u]=low[v];
}
else if(vis[v]&&low[u]>dfn[v]) low[u]=dfn[v];
}
if(dfn[u]==low[u])
{
scc++;
do
{
v=sta[--top];
vis[v]=;
belg[v]=scc;
num[scc]++;
}
while(v!=u) ;
}
}
bool Tarjan(int n)
{
memset(vis,,sizeof(vis));
memset(dfn,,sizeof(dfn));
memset(num,,sizeof(num));
memset(low,,sizeof(low));
index=scc=top=;
for(int i=;i<n;i++)
if(!dfn[i]) tarbfs(i);
for(int i=;i<n;i+=)
if(belg[i]==belg[i+]) return ;
return ;
}
void init()
{
tot=;
memset(head,-,sizeof(head));
}
void addedge(int i,int j)
{
edge[tot].to=j; edge[tot].next=head[i];head[i]=tot++;
}
struct point
{
int x,y;
}s[N];
double Distance(point a, point b)
{
return sqrt((double)(a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int main()
{
//freopen("test.txt","r",stdin);
int n,i,j,k;
double left,right,mid;
while(scanf("%d",&n)!=EOF)
{
for(i=;i<n;i++)
scanf("%d%d%d%d",&s[*i].x,&s[*i].y,&s[*i+].x,&s[*i+].y);
left=; right=40000.0;
n*=;
while(right-left>=eps)
{
mid=(left+right)/;
init();
for(i=;i<n-;i++)
{
if(i%==) k=i+;
else k=i+;
for(j=k;j<n;j++)
if(Distance(s[i],s[j])<*mid)
{
addedge(i,j^);
addedge(j,i^);
}
}
if(Tarjan(n)) left=mid;
else right=mid;
}
printf("%0.2f\n",right);
}
return ;
}

第三种题型,强连通+拓扑排序求任意解。

典型题目:poj3622
讲得特别好的博客: http://blog.csdn.net/qq172108805/article/details/7603351
题意:有对情侣结婚,请来n-1对夫妇,算上他们自己共n对,编号为0~~n-1,他们自己编号为0.所有人坐在桌子两旁,新娘不想看到对面的人有夫妻关系或偷奸关系,若有解,输出一组解,无解输出bad luck 。
题目分析:
  题目的大致意思可以理解为把HDU1814求字典序最小的解改为求任意解。
这一类的题目的做法是:
  第一步,建图。规则是边<u,v>表示选u就必须选v。
  第二步,求强连通分量。这一步我觉得就是用模版了。
  第三步,对每一对点进行判断,如果它们属于同一强连通,就无解。有解才进行下面的步骤。比如此题,通奸的人不能做同一边。
  第四步,对缩点后的图进行反向建图。
  第五步,对建的图进行拓扑排序染色。选择一个没有被染色的点a,将其染成红色,把所有与a点矛盾的点b和b的子孙染成黑色。不断重复操作,知道没有点可以染色而止。
  最后,红色的点就是一个解。
时间复杂度是O(m),主要是求强连通分量。
要注意的是:强连通缩点以后,建立的是反向图。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =, M=;
struct node
{
int to, next;;
}edge[M],e[M];
int stk[N],stk2[N],head[N],low[N],belg[N];
int id[N],vis[N],h[N], t, que[N],dui[N];
int cn,cm,tot,scc,lay;
int Garbowbfs(int cur,int lay)
{
stk[++cn]=cur; stk2[++cm]=cur;
low[cur]=++lay;
for(int i=head[cur];i!=-;i=edge[i].next)
{
int v=edge[i].to;
if(!low[v]) Garbowbfs(v,lay);
else if(!belg[v])
while(low[stk2[cm]]>low[v]) cm--;
}
if(stk2[cm]==cur)
{
cm--;
scc++;
do
belg[stk[cn]]=scc;
while(stk[cn--]!=cur) ;
}
return ;
} bool Garbow(int n)
{
scc=lay=;
memset(belg,,sizeof(belg));
memset(low,,sizeof(low));
for(int i=;i<n;i++)//顶点从0开始(若是1需要修改)
if(!low[i]) Garbowbfs(i,lay);
for(int i=;i<n;i+=)
{
if(belg[i]==belg[i+]) return ;
dui[belg[i]]=belg[i+];
dui[belg[i+]]=belg[i];
}
return ;
}
void addedge(int i,int j)
{
edge[tot].to=j; edge[tot].next=head[i];head[i]=tot++;
}
void add_e(int i,int j)
{
e[t].to=j; e[t].next=h[i];h[i]=t++;
}
void init()
{
tot=t=;
memset(head,-,sizeof(head));
memset(h,-,sizeof(h));
memset(vis,-,sizeof(vis));
memset(id,,sizeof(id));
}
void topo()
{
int i,k,j=;
for(i=;i<scc;i++)
if(id[i]==) que[j++]=i;
for(i=;i<j;i++)
{
int u=que[i];
if(vis[u]==-)
{
vis[u]=;
vis[dui[u]]=;
}
for(k=h[u];k!=-;k=e[k].next)
{
int v=e[k].to;
id[v]--;
if(id[v]==)
que[j++]=v;
}
}
}
int main()
{
//freopen("test.txt","r",stdin);
int n,m,i,j,k;
char ch1,ch2;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(!n) break;
init();
while(m--)
{
scanf("%d%c %d%c",&i,&ch1,&j,&ch2);
if(ch1=='w') i=*i;
else i=*i+;
if(ch2=='w') j=*j;
else j=*j+;
addedge(i,j^);
addedge(j,i^);
}
addedge(,);
n*=;
if(Garbow(n)==) {printf("bad luck\n"); continue;}
for(i=;i<n;i++)//反向图
{
for(k=head[i];k!=-;k=edge[k].next)
{
int u=belg[i], v=belg[edge[k].to];
if(u!=v)
{
add_e(v,u);
id[u]++;
}
}
}
topo();
for(i=;i<n;i+=)
{
k=belg[i];
if(vis[k]==)printf("%dh ",i/);
else printf("%dw ",i/);
}
printf("\n");
}
return ;
}

 

 

2-SAT 小结的更多相关文章

  1. python datetime 时间日期处理小结

    python datetime 时间日期处理小结 转载请注明出处:http://hi.baidu.com/leejun_2005/blog/item/47f340f1a85b5cb3a50f5232. ...

  2. python formatters 与字符串 小结 (python 2)

    最近学习python 2 ,觉得有必要小结一下关于字符串处理中的formatters, 转载请声明本文的引用出处:仰望大牛的小清新 0.%进行变量取值使用的时机 在python中,如果我们只是需要在字 ...

  3. 从零开始编写自己的C#框架(26)——小结

    一直想写个总结,不过实在太忙了,所以一直拖啊拖啊,拖到现在,不过也好,有了这段时间的沉淀,发现自己又有了小小的进步.哈哈...... 原想框架开发的相关开发步骤.文档.代码.功能.部署等都简单的讲过了 ...

  4. Python自然语言处理工具小结

    Python自然语言处理工具小结 作者:白宁超 2016年11月21日21:45:26 目录 [Python NLP]干货!详述Python NLTK下如何使用stanford NLP工具包(1) [ ...

  5. java单向加密算法小结(2)--MD5哈希算法

    上一篇文章整理了Base64算法的相关知识,严格来说,Base64只能算是一种编码方式而非加密算法,这一篇要说的MD5,其实也不算是加密算法,而是一种哈希算法,即将目标文本转化为固定长度,不可逆的字符 ...

  6. iOS--->微信支付小结

    iOS--->微信支付小结 说起支付,除了支付宝支付之外,微信支付也是我们三方支付中最重要的方式之一,承接上面总结的支付宝,接下来把微信支付也总结了一下 ***那么首先还是由公司去创建并申请使用 ...

  7. iOS 之UITextFiled/UITextView小结

    一:编辑被键盘遮挡的问题 参考自:http://blog.csdn.net/windkisshao/article/details/21398521 1.自定方法 ,用于移动视图 -(void)mov ...

  8. K近邻法(KNN)原理小结

    K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用.比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出 ...

  9. scikit-learn随机森林调参小结

    在Bagging与随机森林算法原理小结中,我们对随机森林(Random Forest, 以下简称RF)的原理做了总结.本文就从实践的角度对RF做一个总结.重点讲述scikit-learn中RF的调参注 ...

  10. Bagging与随机森林算法原理小结

    在集成学习原理小结中,我们讲到了集成学习有两个流派,一个是boosting派系,它的特点是各个弱学习器之间有依赖关系.另一种是bagging流派,它的特点是各个弱学习器之间没有依赖关系,可以并行拟合. ...

随机推荐

  1. Windows server 2008R2系统登录密码破解

    服务器密码忘记,或者被恶意修改,系统被入侵,都是很让人烦心的事情,我试过很多方法,包括使用PE工具删除C盘Windows\System\config里面的SAM文件,可是过程都相当华美,结果都相当杯具 ...

  2. markdown让文字居中和带颜色

    markdown让文字居中和带颜色 markdown让文字居中和带颜色1.说明2. 文字的居中3.文字的字体及颜色3.1 字体更换3.2 大小更换3.3 颜色替换4 总结 1.说明 本文主要叙述如何写 ...

  3. 基于 Nginx XSendfile + SpringMVC 进行文件下载

    转自:http://denger.iteye.com/blog/1014066 基于 Nginx XSendfile + SpringMVC 进行文件下载 PS:经过实际测试,通过 nginx 提供文 ...

  4. code runner运行终端的目录设置

    我的github:swarz,欢迎给老弟我++星星 该设置属性为 "code-runner.fileDirectoryAsCwd": true 设置为 true后,终端默认目录为运 ...

  5. [LeetCode] 20. 有效的括号 (栈)

    思路: 首先用字典将三对括号存储,遍历字符串中每个字符,遇到左括号就入栈:遇到右括号就开始判断:是否与栈弹出的顶字符相同. 如果到最后栈被清空,说明全部匹配上了,为真. class Solution( ...

  6. Asp.Net使用Yahoo.Yui.Compressor.dll压缩Js|Css

    网上压缩css和js工具很多,但在我们的系统中总有特殊的地方.也许你会觉得用第三方的压缩工具很麻烦.我就遇到了这样问题,我不想在本地压缩,只想更新到服务器上去压缩,服务器压缩也不用备份之类的操作.于是 ...

  7. VS2015 C++ 获取 Edit Control 控件的文本内容,以及把获取到的CString类型的内容转换为 int 型

    UpdateData(true); //读取编辑框内容,只要建立好控件变量后调用这个函数使能,系统就会自动把内容存在变量里 //这里我给 Edit Control 控件创建了一个CString类型.V ...

  8. 如鹏网JAVA培训笔记1(晓伟整理)

    JDK(Java Developmet Kit) JRE(Java RunTime Environment)的区别: JRE只有运行JAVA程序的环境,没有开发相关的工具;JDK=JRE+开发相关的工 ...

  9. 洛谷—— P2690 接苹果

    https://www.luogu.org/problem/show?pid=2690 题目背景 USACO 题目描述 很少有人知道奶牛爱吃苹果.农夫约翰的农场上有两棵苹果树(编号为1和2), 每一棵 ...

  10. jQuery中的closest()和parents()的差别

    jQuery中的closest()和parents()的差别 jQuery中closest()和parents()的作用非常类似,都是向上寻找符合选择器条件的元素,可是他们之间有一些细微的差别,官网也 ...