2-SAT问题的解法(uva1146)

SAT:就是一些由布尔值组成的关系的集合。

2-SAT:就是由两个布尔值组成的关系的集合。

2-SAT问题:就是给出一些关系,然后问能不能满足这些所有的关系?

现在比如说有n个国家,每个国家有两个代表,必须选出一个代表参加一个国际会议,但是有些代表之间有矛盾,现在给出这些矛盾的代表,问能不能选出满足条件的。

4个国家,代表编号为2*i,2 *i-1

这些代表有矛盾1和4,2和3,7和3

这样的话肯定是能够满足条件的。

这篇论文讲的很清晰

点击

其中给出了两种解法,首先对于上面的例子,1和4有矛盾,很明显分别属于1和2国家,那么加入我要选1则必须选3,因为每个国家必须选一个,而1和4又是矛盾的!同样要选4必须选2,那么我可以给他们必选条件之间建一条有向边。

那么对于上面样例可以得到这样一个图

这样建图之后,能够得到一种很直观的解法。

枚举所有的同一个国家的代表(2*i 和 2 *i-1)首先任选一个,推导出相关的,若不矛盾,则可行,否则选另一个,若也不可行,则无解。

这个算法的时间负责度O(m*n),在大多数情况下是可行的。

其实可以更优,首先我们发现图中存在很多环,对环缩点是对原图情况没有影响的,同样,在同一个环中的点必然是要么都选,要么都不选,那么如果存在在一个换中有同一个国家的两个代表的话,这样肯定是不可行的。

那么就得到了一个基于对称性的算法,建图,缩点,缩在同一个环上的点判断是否是同一个国家,不在则无解。算法负责度O(m)

UVA1146这个题目是一个类似的题目,不过要求一个最大值,我们二分结果,然后用2-set判断是否可行。

Tarjan算法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
using namespace std;
const int N = 2200;
int tim[N][3],dfs_clock,tpnum;
stack<int> sta;
struct TwoSet
{
    vector<int> G[2*N];
    bool vis[2*N];
    int dfn[2*N],low[2*N],tp[2*N];
    void init(int n)
    {
        for(int i=0;i<=2*n;i++)
            G[i].clear();
        dfs_clock = tpnum = 0;
        memset(vis,false,sizeof(vis));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(tp,0,sizeof(tp));
        while(!sta.empty())
            sta.pop();

    }
    void add_Node(int x,int valx,int y,int valy)
    {
        x = 2*x + valx;
        y = 2*y + valy;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }
    void Tarjan(int x)
    {
        sta.push(x);
        vis[x] = true;
        dfn[x] = low[x] = ++dfs_clock;
        for(int i=0;i<G[x].size();i++)
        {
            int y = G[x][i];
            if(!dfn[y])
            {
                Tarjan(y);
                low[x] = min(low[x],low[y]);
            }
            else
            {
                if(vis[y])
                    low[x] = min(low[x],dfn[y]);
            }
        }
        if(low[x] == dfn[x])
        {
            tpnum++;
            do
            {
                x = sta.top();
                sta.pop();
                vis[x] = false;
                tp[x] = tpnum;
            }while(low[x] != dfn[x]);
        }
    }
    bool yougth(int n)
    {
        for(int i=0;i<2*n;i++)
            if(!dfn[i])
                Tarjan(i);
        for(int i=0;i<n;i++)
            if(tp[2*i]==tp[2*i+1])
                return false;
        return true;
    }
};
TwoSet solver;
bool test(int diff,int n)
{
    solver.init(n);
    for(int i=0;i<n;i++)for(int a=0;a<2;a++)
        for(int j=i+1;j<n;j++)for(int b=0;b<2;b++)
            if(abs(tim[i][a]-tim[j][b])<diff)solver.add_Node(i,a^1,j,b^1);
    return solver.yougth(n);
}
int main()
{
    //freopen("Input.txt","r",stdin);
    int n;
    while(~scanf("%d",&n))
    {
        int L = 0,R = 0;
        for(int i=0;i<n;i++)
        {
            for(int t=0;t<2;t++)
            {
                scanf("%d",&tim[i][t]);
                R = max(R,tim[i][t]);
            }
        }
        while(L<R)
        {
            int mid=L+(R-L+1)/2;
            if(test(mid,n))L=mid;
            else R=mid-1;
        }
        printf("%d\n",L);
    }
    return 0;
}

直接暴力判断算法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 2200;
int tim[N][3];

struct TwoSet
{
    vector<int> G[2*N];
    bool vis[2*N];
    int s[2*N],c;
    void init(int n)
    {
        for(int i=0;i<=2*n;i++)
            G[i].clear();
        memset(vis,false,sizeof(vis));
    }
    void add_Node(int x,int valx,int y,int valy)
    {
        x = 2*x + valx;
        y = 2*y + valy;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }
    bool dfs(int x)
    {
        if(vis[x^1])
            return false;
        if(vis[x])
            return true;
        vis[x] = true;
        s[c++] = x;
        for(int i=0;i<G[x].size();i++)
        {
            if(!dfs(G[x][i]))
                return false;
        }
        return true;
    }
    bool yougth(int n)
    {
        for(int i = 0;i< 2*n;i+=2)
        {
            if(!vis[i] && !vis[i+1])
            {
                c = 0;
                if(!dfs(i))
                {
                    while(c>0)
                        vis[ s[--c] ] = false;
                    if(!dfs(i+1))
                        return false;
                }
            }
        }
        return true;
    }
};
TwoSet solver;
bool test(int diff,int n)
{
    solver.init(n);
    for(int i=0;i<n;i++)for(int a=0;a<2;a++)
        for(int j=i+1;j<n;j++)for(int b=0;b<2;b++)
            if(abs(tim[i][a]-tim[j][b])<diff)solver.add_Node(i,a^1,j,b^1);
    return solver.yougth(n);
}
int main()
{
    //freopen("Input.txt","r",stdin);
    int n;
    while(~scanf("%d",&n))
    {
        int L = 0,R = 0;
        for(int i=0;i<n;i++)
        {
            for(int t=0;t<2;t++)
            {
                scanf("%d",&tim[i][t]);
                R = max(R,tim[i][t]);
            }
        }
        while(L<R)
        {
            int mid=L+(R-L+1)/2;
            if(test(mid,n))L=mid;
            else R=mid-1;
        }
        printf("%d\n",L);
    }
    return 0;
}
时间: 2024-08-29 22:52:17

2-SAT问题的解法(uva1146)的相关文章

2-SAT 问题与解法小结

2-SAT 问题与解法小结 这个算法十分的奇妙qwq... 将一类判定问题转换为图论问题,然后就很容易解决了. 本文有一些地方摘录了一下赵爽<2-SAT解法浅析> (侵删) 一些概念: \(SAT\)问题:就是给一些布尔变量赋值,使得所有给你的条件成立的问题---适定性(Satisfiability)问题.我们令\(k\)为所有条件中含有变量的最大值,那么我们就可以称其为\(k-SAT\)问题. 可以证明\(k>2\)时候为NP完全问题,而\(k=2\)的时候存在多项式解法. \(2-S

SAT考试里最难的数学题? &middot; 三只猫的温暖

问题 今天无意中在Quora上看到有人贴出来一道号称是SAT里最难的一道数学题,一下子勾起了我的兴趣.于是拿起笔来写写画画,花了差不多十五分钟搞定.觉得有点意思,决定把解题过程记下来.原帖的图太小,我用GeoGebra重新画了一遍.没错,我就是强迫症. 为了省事,就把这道题叫做RASBTC. In the figure above, arc (text{SBT}) is one quarter of a circle with center (text{R}) and radius 6. If

一个不简洁的约瑟夫环解法

约瑟夫环类似模型:已知有n个人,每次间隔k个人剔除一个,求最后一个剩余的. 此解法为变种,k最初为k-2,之后每次都加1. 例:n=5,k=3.从1开始,第一次间隔k-2=1,将3剔除,第二次间隔k-1=2,将1剔除.依此类推,直至剩余最后一个元素. 核心思路:将原列表复制多份横向展开,每次根据间隔获取被剔除的元素,同时将此元素存入一个剔除列表中.若被剔除元素不存在于剔除列表,则将其加入,若已存在,则顺势后移至从未加入剔除列表的元素,并将其加入.如此重复n-1次.面试遇到的题,当时只写了思路,没

斐波那契数列的递归和非递归解法

//递归解法 function fib(n){ if(n < 1){ throw new Error('invalid arguments'); } if(n == 1 || n == 2){ return 1; } return fib(n - 1) + fib(n - 2); } //非递归解法 function fib(n){ if(n < 1){ throw new Error('invalid arguments'); } if(n == 1 || n == 2){ return 1

HDU1013 POJ1519 Digital Roots(解法三)

该问题的最佳解法是利用数论的9余数定理来计算数根.一个数的数根等于该数的9的余数,若余数为0则结果为9. 问题链接:HDU1013 POJ1519 Digital Roots.入门练习题,用C语言编写程序. 问题简述:输入若干正整数,求其数根,直到输入为0为止. 问题分析:数根是指整数的各个位的数字之和.如果其和为1位整数,则为结果:如果其和为多位整数,则再将各位数字相加,直到其和为1位数为止.这个问题的大陷阱是,没有指出整数是多少位的.即使使用unsignde long long类型,也可能会

ACM/ICPC 之 四道MST-Prim解法(POJ1258-POJ1751-POJ2349-POJ3026)

四道MST,适合Prim解法,也可以作为MST练习题. 题意包括在代码中. POJ1258-Agri Net 水题 1 //Prim-没什么好说的 2 //接受一个邻接矩阵,求MST 3 //Time:0Ms Memory:220K 4 #include<iostream> 5 #include<cstring> 6 #include<cstdio> 7 #include<algorithm> 8 using namespace std; 9 #define

hdu4521 小明系列问题——小明序列(LIS变种 (线段树+单点更新解法))

链接: huangjing 题目:中文题目 思路: 这个题目如果去掉那个距离大于d的条件,那么必然是一个普通的LIS,但是加上那个条件后就变得复杂了.用dp的解法没有看懂,我用的线段树的解法...就是采用延迟更新的做法,用为距离要大于d啊,所以我们在循环到第i的时候,就对(i-d-1)这个点进行更新,因为如果在(i-d-1)这个点更新了,会对后面的造成影响,然后线段树的tree[]数组存的是以i结尾的最长lis,那么每次询问的时候就找最大的tree[]就可以了... 代码: 小明系列问题--小明

LA 3211 飞机调度(2—SAT)

https://vjudge.net/problem/UVALive-3211 题意: 有n架飞机需要着陆,每架飞机都可以选择“早着陆”和“晚着陆”两种方式之一,且必须选择一种,第i架飞机的早着陆时间为E,晚着陆时间为L,不得在其他时间着陆.你的任务是为这些飞机安排着陆方式,使得整个着陆计划尽量安全.换句话说,如果把所有飞机的实际着陆时间按照从早到晚的顺序排列,相邻两个着陆时间间隔的最小值. 思路: 二分查找最大值P,每次都用2—SAT判断是否可行. 1 #include<iostream>

最近点对问题的解法

回到二维的情况.参考一维退化版本的解法,首先进行分解和求解步骤. 分解:将所有点按照横坐标从中心分成两部分. 求解:递归求解两半部分的最近点对,左右两半部分依次为 p_1,p_2p?1??,p?2?? 和 q_1,q_2q?1??,q?2??. 合并:令 d = min\{|p_1-p_2|, |q_1-q_2|\}d=min{∣p?1??−p?2??∣,∣q?1??−q?2??∣}.如下左图所示,除了两半部分各自的最近点对之外,只需要计算横跨图中左右两半部分区域的点对的距离最小值,再和 dd