6道2-SAT题 (模板)

来源:互联网 发布:50 seo 编辑:程序博客网 时间:2024/05/01 13:42

文字部分转帖:http://hi.baidu.com/novosbirsk/blog/item/723a9727a9ab8804918f9dca.html

poj 3207 http://blog.sina.com.cn/s/blog_64675f540100k13v.html

poj 3678 http://blog.sina.com.cn/s/blog_64675f540100k15b.html

poj 3683 http://blog.sina.com.cn/s/blog_64675f540100k1cd.html

poj 3648 http://blog.sina.com.cn/s/blog_64675f540100k1g9.html

poj 2723 http://blog.sina.com.cn/s/blog_64675f540100k2rh.html

poj 2749 http://blog.sina.com.cn/s/blog_64675f540100k2xf.html

最近花了几天的时间做掉了PKU上的六道2-sat的题目,有几题比较简单,构图完成套模板就是了,有两题需要二分答案,有写题目构图容易出错,总之收获良多。

不懂2-sat的朋友推荐在Google上搜索“2-sat”,应该能找到《由对称性解2-SAT问题》(伍昱的WC论文)、《2-SAT解法浅析》(赵爽)这两篇很好的论文,最好把它们读一读,起码可以对2-SAT模型有个初步了解和认识。关于这个模型的概念和意义我就不详细解说了,还是和上一遍后缀数组的学习心得一样,我这里只是把我的一些心得体会写出来和大家分享,如果有错大家可以指出。

一、关于模型:

一个2-SAT模型应该是一个满足以下的条件的满足性问题:

1、该模型中存在2n个可以分成n组的元素,每组两个元素。

2、每组元素中,选择了其中一个元素,另外一个元素就不能被选择。这两个元素记为a和!a。

3、该模型中的元素之间存在一些关系,且这些关系是对称的。(除非是同一组元素中的关系,这些关系限定了“必须选择”该组中的某一个元素,可能单独出现)

满足上述条件,要求在满足给定关系的情况下在每组元素中选出一个元素的问题称为2-SAT问题。问是否存在即2-SAT判定问题,当然也可以求出一组可行解。

(上面的关于2-SAT的定义是非常不严谨不专业的,仅为帮助大家理解..可能有错,望大家指出..)

当你看见一道题目,一些事物只有唯一且互斥的两种选择(比如两种取值,两种连接方式等等),那么可以分析下题目给出的条件是否满足对称性,若满足则可通过构图将题目转化成2-SAT问题了。

二、关于构图

要解2-SAT问题或者完成判定问题,首要的任务是构图。从《由对称性解2-SAT问题》这篇论文里,我们可以知道,构图的关键是找到冲突。

若a和b冲突,即选a时不能选b,那么选a时必须选!b(因为不选b就必须选!b,这是一个2-SAT问题必须满足的条件),那么我们就连边<a,!b>。同样的道理,如果选了b,那么就不能选a,必须选!a,所以连边<b,!a>。这样的连边,显然是对称的。具体的例子可以看上面的论文。

总之,弄清楚一点:如果存在边<a,b>,那么表示选择a时必须选择b。只要将所有这种“必须”关系都变成边,然后再判定2-sat或者求解2-sat,就能解决2-SAT问题了。

三、关于解2-SAT

方法是,对原图求一次强连通分量,然后看每组中的两个点是否属于同一个强连通分量,如果存在这种情况,那么无解

然后对于缩点后的图G',我们将G'中所有边转置。进行拓扑排序。
   对于缩点后的所有点,我们先预处理求出所有冲突顶点。例如缩点后Ai所在强连通分支的ID
   为id[ Ai] ,同理~Ai在 id[ ~Ai ],所以冲突顶点
    conflict[ id[Ai] ]=conflict[ id[~Ai] ];
同理conflict[ id[~Ai] ]=conflict[ id[Ai] ];

    设缩点后有Nscc个点。
    然后对拓扑序进行染色,初始化所有点color均为未着色
    顺序遍历得到的拓扑序列,对于未着色的点x,将x染成红色,同时将所有与x矛盾的点conflic[x]染成
蓝色。

2-sat的一组解就等价于所有缩点后点颜色为红色的点,也就是color[ id[i] ]=RED的所有点


题目描述:有n个婚礼,每个婚礼有起始时间si,结束时间ti,还有一个主持时间ti,ti必须安排在婚礼的开始或者结束,主持由祭祀来做,但是只有一个祭祀,所以各个婚礼的主持时间不能重复,问你有没有可能正常的安排主持时间,不能输出no,能的话要输出具体的答案:即每个婚礼的主持时间段是什么样的。解题报告:对于每个婚礼,主持时间只有两种状态,而且各个婚礼之间的主持时间之间有相互限制,所以想到2-sat。构图:对于婚礼i和婚礼j。i表示在开始主持,i2表示在结束主持,j类似。枚举每一对不同的i和j。如果i和j冲突。连接i j2如果i和j2冲突,连接i j如果i2和j冲突,连接i2 j2如果i2和j2冲突,连接i2 j然后就是求强联通,topsort,输出答案。代码如下:#include<iostream>using namespace std;#define size 2100 // 点的个数#define esize 3000000 // 边的个数int v[size], cnt, v2[size], cnt2;struct edge{int from, to, next;}e[esize], e2[esize];void insert(int from, int to){    e[cnt].from = from, e[cnt].to = to; e[cnt].next = v[from]; v[from] = cnt++;}void insert2(int from, int to){    e2[cnt2].from = from, e2[cnt2].to = to; e2[cnt2].next = v2[from]; v2[from] = cnt2++;}int index, dfn[size], low[size], instack[size], sta[size], top;int belong[size], cntnum, num[size];int cf[size], rd[size], que[size], col[size];bool ans[size];//1表示选择void tarjan(int id)    //求强联通{    dfn[id] = low[id] = ++index;    instack[id] = 1; sta[top++] = id;    int tmp = v[id];    while(tmp != -1)    {        if (!dfn[e[tmp].to])        {            tarjan(e[tmp].to);            if (low[e[tmp].to] < low[id]) low[id] = low[e[tmp].to];        }        else if (instack[e[tmp].to] && dfn[e[tmp].to] < low[id])            low[id] = dfn[e[tmp].to];        tmp = e[tmp].next;    }    if (dfn[id] == low[id])    {        do        {            tmp = sta[--top]; instack[tmp] = 0;            belong[tmp] = cntnum;            num[cntnum]++;        }while(tmp != id);        cntnum++;    }}bool solve(int n) // n是一半的人数 执行tarjan和topsort(拓扑排序),完成标记{    index = cntnum = top = 0;    memset(dfn, 0, sizeof(dfn));    memset(num, 0, sizeof(num));    for(int i = 0; i < 2 * n; i++)        if (!dfn[i]) tarjan(i);    for(int i = 0; i < n; i++)    {                              //a和a'在同一个强连通分量,无解        if (belong[i] == belong[i + n]) return false;         cf[belong[i]] = belong[i + n];   //标记冲突点        cf[belong[i + n]] = belong[i];    }    memset(rd, 0, sizeof(rd));    memset(v2, -1, sizeof(v2));    memset(col, 0, sizeof(col));cnt2 = 0;    for(int i = 0; i < cnt; i++)        if (belong[e[i].from] != belong[e[i].to])         {                           //缩点建图,转置,建反向边            insert2(belong[e[i].to], belong[e[i].from]);            rd[belong[e[i].from]]++;  //记录入度        }    int head = 0, tail = 0;    for(int i = 0; i < cntnum; i++)  //入度为0的,说明原图出度为0,优先选这些点。        if (rd[i] == 0) que[tail++] = i;    while(head < tail)    {        int tmp = que[head++];        if (col[tmp] == 0)  //对为染色的图        {            col[tmp] = 1;  //染成红色            col[cf[tmp]] = -1;//和它冲突的点染成蓝色        }        int id = v2[tmp];        while(id != -1)//下上看与tmp连得点。        {            if (--rd[e2[id].to] == 0)//这次扫到这个点,所以先自减,入度为1的加入队列                que[tail++] = e2[id].to;            id = e2[id].next;        }    }    memset(ans, 0, sizeof(ans));    for(int i = 0; i < 2 * n; i++)//标记所有红色的点。        if (col[belong[i]] == 1) ans[i] = 1;    return true;}int n;struct elm{int from, to, len;}x[size];char str[2][10];int judge(int id){    int num = (str[id][0] - '0') * 10;    num += (str[id][1] - '0'); num *= 60;    num += (str[id][3] - '0') * 10 + str[id][4] - '0';    return num;}bool judge2(int from1, int len1, int from2, int len2){    return (from1 < from2 + len2 && from2 < from1 + len1);}void judge3(int xx){    printf("%02d:%02d", xx / 60, xx % 60);}int main(){    scanf("%d", &n);    for(int i = 0; i < n; i++)    {        scanf("%s%s%d", str[0], str[1], &x[i].len);        x[i].from = judge(0);        x[i].to = judge(1);    }    memset(v, -1, sizeof(v)); cnt = 0;    for(int i = 0; i < n; i++)        for(int j = 0; j < n; j++)        {            if (i == j) continue;            if (judge2(x[i].from, x[i].len, x[j].from, x[j].len))                insert(i, j + n);            if (judge2(x[i].from, x[i].len, x[j].to - x[j].len, x[j].len))                insert(i, j);            if (judge2(x[i].to - x[i].len, x[i].len, x[j].from, x[j].len))                insert(i + n, j + n);            if (judge2(x[i].to - x[i].len, x[i].len, x[j].to - x[j].len, x[j].len))                insert(i + n, j);        }    if(solve(n))    {        printf("YES\n");        for(int i = 0; i < n; i++)            if (ans[i])        //染成红色的表示选择这个点            {                judge3(x[i].from); putchar(' ');                judge3(x[i].from + x[i].len); putchar('\n');            }            else            {                judge3(x[i].to - x[i].len); putchar(' ');                judge3(x[i].to); putchar('\n');            }    }    else printf("NO\n");    return 0;}


0 0
原创粉丝点击