2-SAT专题(1)

附带我的学习来源,这里包含以上所有题目的题解和代码。实在想不出来我就是看这里的。

1. HDU 3062 Party

题意:
有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席? (中文题面就复制粘贴一下了……)

思路:
非常典型的 2-sat 模型,如果两对夫妻 A B ,丈夫A0 与 妻子B1 有仇,那么就建 A0 -> B0 , A1 -> B1 两条边。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 2005;

int idx, n, m, scc, top, vis_time;
int head[maxn], dfn[maxn], low[maxn], in[maxn], color[maxn], st[maxn];
bool instack[maxn];

struct node {
    int to;
    int nxt;
} edges[1005 * 1005 * 2];

void addEdge(int u, int v)
{
    edges[idx].to = v;
    edges[idx].nxt = head[u];
    head[u] = idx++;
}

void init()
{
    for (int i = 0; i <= 2 * n; i++) {
        head[i] = -1;
        in[i] = dfn[i] = low[i] = 0;
    }
    vis_time = top = idx = scc = 0;
}

void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = vis_time++;
    instack[u] = true;
    st[++top] = u;
    for (int id = head[u]; ~id; id = edges[id].nxt) {
        v = edges[id].to;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (instack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        scc++;
        do {
            v = st[top--];
            instack[v] = false;
            color[v] = scc;
        } while (v != u);
    }
}

int main()
{
    int a1, a2, c1, c2;
    while (scanf("%d %d", &n, &m) != EOF) {
        init();
        for (int i = 0; i < m; i++) {
            scanf("%d %d %d %d",&a1,&a2,&c1,&c2);
            addEdge((a1 << 1) + c1, (a2 << 1 | 1) - c2);
            addEdge((a2 << 1) + c2, (a1 << 1 | 1) - c1);
        }
        for (int i = 0; i < 2 * n; i++)
            if (dfn[i] == 0)
                tarjan(i);
        bool flag = true;
        for (int i = 0; i < n; i++)
            if (color[i << 1] == color[i << 1 | 1]) {
                flag = false;
                break;
            }
        puts(flag ? "YES" : "NO");
    }
    return 0;
}

2. HDU 1824 Let’s go home

题意:
入门2-sat,稍微有点的变化就是两方中有一方是集合。

思路:
其实这并没有什么关系,就算是两个集合,A,B( 第i对 ),那么把 A 的所有集合赋值为 i<<1,B 则为 i<<1|1 。再根据他的要求,由编号索引到集合号,建边即可。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 1010, kmax = 5050;

int n, m, idx, scc, top, vis_time;
int kpair[kmax];
int head[maxn << 1];
int dfn[kmax], low[kmax], st[kmax], color[kmax];
bool instack[kmax];

struct node {
    int to;
    int nxt;
} edges[kmax];

void addEdge(int u, int v)
{
    edges[idx].to = v;
    edges[idx].nxt = head[u];
    head[u] = idx++;
}

void init()
{
    memset(head, -1, sizeof head);
    memset(dfn, -1, sizeof dfn);
    memset(instack, false, sizeof instack);
    vis_time = top = idx = scc = 0;
}

void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = vis_time++;
    instack[u] = true;
    st[++top] = u;
    for (int id = head[u]; ~id; id = edges[id].nxt) {
        v = edges[id].to;
        if (dfn[v] == -1) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (instack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        scc++;
        do {
            v = st[top--];
            instack[v] = false;
            color[v] = scc;
        } while (v != u);
    }
}

int main()
{
    int a, b, c, ka, kb;
    while (scanf("%d %d", &n, &m) != EOF) {
        init();
        for (int i = 0; i < n; i++) {
            scanf("%d %d %d", &a, &b, &c);
            kpair[a] = i << 1;
            kpair[b] = i << 1 | 1;
            kpair[c] = i << 1 | 1;
        }
        for (int i = 0; i < m; i++) {
            scanf("%d %d", &a, &b);
            ka = kpair[a];
            kb = kpair[b];
            addEdge(ka, kb ^ 1);
            addEdge(kb, ka ^ 1);
        }
        for (int i = 0; i < 2 * n; i++)
            if (dfn[i] == -1)
                tarjan(i);
        bool flag = true;
        for (int i = 0; i < n; i++)
            if (color[i << 1] == color[i << 1 | 1]) {
                flag = false;
                break;
            }
        puts(flag ? "yes" : "no");
    }
    return 0;
}

3. POJ 3207 Ikki’s Story IV – Panda’s Trick

题意:
一个圆上有 n 个点,现在有 m 条边分别连接这 n 个点。连接方式分在圆内和圆外。问是否存在这样一种方案能满足这 m 条边。

思路:
对于这 m 条边我们两两进行判断。如果存在相交情况,那么这两条线将不能在同一侧,相交效果见下图。

这在一维的表现是两条线段,相交但不包含。不包含!!!不包含!!!!

值得一提的是,对于两条线段,如果在圆内是相交的,那么在圆外也必然相交。所以一次判断相交要建立四条边。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 2010, kmax = 5050;

int n, m, idx, scc, top, vis_time;
int kpair[kmax];
int head[maxn << 1], le[maxn], ri[maxn];
int dfn[kmax], low[kmax], st[kmax], color[kmax];
bool instack[kmax];

struct node {
    int to;
    int nxt;
} edges[505 * 505 * 4];

void addEdge(int u, int v)
{
    edges[idx].to = v;
    edges[idx].nxt = head[u];
    head[u] = idx++;
}

void init()
{
    memset(head, -1, sizeof head);
    memset(dfn, -1, sizeof dfn);
    memset(instack, false, sizeof instack);
    vis_time = top = idx = scc = 0;
}

void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = vis_time++;
    instack[u] = true;
    st[++top] = u;
    for (int id = head[u]; ~id; id = edges[id].nxt) {
        v = edges[id].to;
        if (dfn[v] == -1) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (instack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        scc++;
        do {
            v = st[top--];
            instack[v] = false;
            color[v] = scc;
        } while (v != u);
    }
}

inline bool judge(int a, int b)
{
    if (le[a] > le[b])
        swap(a, b);
    return ri[a] > le[b] && ri[b] > ri[a];
}

int main()
{
    while (scanf("%d %d", &n, &m) != EOF) {
        init();
        for (int i = 0; i < m; i++) {
            scanf("%d %d", le + i, ri + i);
            if (le[i] > ri[i])
                swap(le[i], ri[i]);
        }
        for (int i = 0; i < m; i++)
            for (int j = i + 1; j < m; j++)
                if (judge(i, j)) {
                    addEdge(i << 1, j << 1 | 1);
                    addEdge(i << 1 | 1, j << 1);
                    addEdge(j << 1, i << 1 | 1);
                    addEdge(j << 1 | 1, i << 1);
                }
        for (int i = 0; i < m * 2; i++)
            if (dfn[i] == -1)
                tarjan(i);
        bool flag = true;
        for (int i = 0; i < m; i++)
            if (color[i << 1] == color[i << 1 | 1]) {
                flag = false;
                break;
            }
        puts(flag ? "panda is telling the truth..." : "the evil panda is lying again");
    }
    return 0;
}

4. POJ 3678 Katu Puzzle

题意:
有n个未知数,取值只能为 0或1,给你 m 个限制。

$$X_i op X_j = c$$

问你是否存在这样 n 个数使得所有限制都成立。

思路:
2-sat建图题,把每个值是1(a)和0(~a)为两种状态。
AND 结果为1:建边 x->x,y->y (两个数必须全为1)
(对于这里我不是很理解,一开始我写的是 x -> y , y -> x 。

AND 结果为0:建边 y->x,x->y (两个数至少有一个为0)
OR 结果为1:建边 x->y,y->x (两个数至少有一个为1)
OR 结果为0:建边 x->x,y->y (两个数必须全为0)
XOR 结果为1:建边 x->y,y->x,y->x,x->y (两个数必须不同)
XOR 结果为0:建边 x->y,y->x,x->y,y->x (两个数必须相同)

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 1010 * 2, kmax = 1e6 + 5;

int n, m, idx, scc, top, vis_time;
int head[maxn];
int dfn[maxn], low[maxn], st[maxn], color[maxn];
bool instack[maxn];

struct node {
    int to;
    int nxt;
} edges[kmax << 1];

void addEdge(int u, int v)
{
    edges[idx].to = v;
    edges[idx].nxt = head[u];
    head[u] = idx++;
}

void init()
{
    memset(head, -1, sizeof head);
    memset(dfn, -1, sizeof dfn);
    memset(instack, false, sizeof instack);
    vis_time = top = idx = scc = 0;
}

void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = vis_time++;
    instack[u] = true;
    st[++top] = u;
    for (int id = head[u]; ~id; id = edges[id].nxt) {
        v = edges[id].to;
        if (dfn[v] == -1) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (instack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        scc++;
        do {
            v = st[top--];
            instack[v] = false;
            color[v] = scc;
        } while (v != u);
    }
}

int main()
{
    int a, b, res;
    char op[5];
    while (scanf("%d %d", &n, &m) != EOF) {
        init();
        for (int i = 0; i < m; i++) {
            scanf("%d %d %d %s", &a, &b, &res, op);
            if (op[0] == 'A') {
                if (res) {
                    addEdge(a << 1, a << 1 | 1);
                    addEdge(b << 1, b << 1 | 1);
                } else {
                    addEdge(a << 1 | 1, b << 1);
                    addEdge(b << 1 | 1, a << 1);
                }
            } else if (op[0] == 'O') {
                if (res) {
                    addEdge(a << 1, b << 1 | 1);
                    addEdge(b << 1, a << 1 | 1);
                } else {
                    addEdge(a << 1 | 1, a << 1);
                    addEdge(b << 1 | 1, b << 1);
                }
            } else {
                if (res) {
                    addEdge(a << 1, b << 1 | 1);
                    addEdge(b << 1, a << 1 | 1);
                    addEdge(a << 1 | 1, b << 1);
                    addEdge(b << 1 | 1, a << 1);
                } else {
                    addEdge(a << 1, b << 1);
                    addEdge(b << 1, a << 1);
                    addEdge(a << 1 | 1, b << 1 | 1);
                    addEdge(b << 1 | 1, a << 1 | 1);
                }
            }
        }
        for (int i = 0; i < 2 * n; i++)
            if (dfn[i] == -1)
                tarjan(i);
        bool flag = true;
        for (int i = 0; i < n; i++)
            if (color[i << 1] == color[i << 1 | 1]) {
                flag = false;
                break;
            }
        puts(flag ? "YES" : "NO");
    }
    return 0;
}

5.HDU 3622 Bomb Game

题意:
有n组炸弹,一组两个,位置固定,你必须从中选出任意一个,使得所有炸弹单独爆炸都不会波及到其他任意炸弹,炸弹范围自定,求最大的炸弹爆炸范围。

思路:
对范围进行二分,对每一个 mid 进行 2-sat 判定即可。

如何建图?
昨天好好反思了一下这个问题,2-sat就是解决二重适应性问题,如果存在一个矛盾,那么就必须对它进行处理,否则,无需处理。例如,如果第 i 组第一个炸弹与第 j 组第一个炸弹范围重叠,那么 i 组 1 号炸弹必须得与 j 组 2 号炸弹相连,在这个条件下,这是必须满足的。因为爆炸是双向的,所以第 j 组 1 号炸弹必须与第 i 组 2 号炸弹相连。

因为爆炸是双向的,所以我们只需要建立 i = (0..n) j = (i+1..n) 的循环即可,否则会代码写丑很容易超时。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int maxn = 205;
const double eps = 1e-6;

int n;
double dis[maxn][2][maxn][2];
int head[maxn];
int instack[maxn], dfn[maxn], low[maxn], st[maxn], color[maxn];
int vis_time, top, idx, scc;

struct double_node {
    int x[2], y[2];
} nodes[maxn];

struct node {
    int to;
    int nxt;
} edges[maxn * maxn * 4];

inline double func(int i, int a, int j, int b)
{
    return (double)(nodes[i].x[a] - nodes[j].x[b]) * (nodes[i].x[a] - nodes[j].x[b]) + (nodes[i].y[a] - nodes[j].y[b]) * (nodes[i].y[a] - nodes[j].y[b]);
}

void addEdge(int u, int v)
{
    edges[idx].to = v;
    edges[idx].nxt = head[u];
    head[u] = idx++;
}

void init()
{
    memset(head, -1, sizeof head);
    memset(dfn, -1, sizeof dfn);
    memset(instack, false, sizeof instack);
    vis_time = top = idx = scc = 0;
}

void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = vis_time++;
    instack[u] = true;
    st[++top] = u;
    for (int id = head[u]; ~id; id = edges[id].nxt) {
        v = edges[id].to;
        if (dfn[v] == -1) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (instack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        scc++;
        do {
            v = st[top--];
            instack[v] = false;
            color[v] = scc;
        } while (v != u);
    }
}

bool judge(double radius)
{
    init();
    radius = 4 * radius * radius;
    for (int i = 0; i < n; i++)
        for (int j = i+1; j < n; j++) {
            if (dis[i][0][j][0] < radius) {
                addEdge(i << 1, j << 1 | 1);
                addEdge(j << 1, i << 1 | 1);
            }
            if (dis[i][0][j][1] < radius) {
                addEdge(i << 1, j << 1);
                addEdge(j << 1 | 1, i << 1 | 1);
            }
            if (dis[i][1][j][0] < radius) {
                addEdge(i << 1 | 1, j << 1 | 1);
                addEdge(j << 1, i << 1);
            }
            if (dis[i][1][j][1] < radius) {
                addEdge(i << 1 | 1, j << 1);
                addEdge(j << 1 | 1, i << 1);
            }
        }
    for (int i = 0; i < 2 * n; i++)
        if (dfn[i] == -1)
            tarjan(i);
    bool flag = true;
    for (int i = 0; i < n; i++)
        if (color[i << 1] == color[i << 1 | 1]) {
            flag = false;
            break;
        }
    return flag ? true : false;
}

int main()
{
    while (scanf("%d", &n) != EOF) {
        for (int i = 0; i < n; i++)
            scanf("%d %d %d %d", &nodes[i].x[0], &nodes[i].y[0], &nodes[i].x[1], &nodes[i].y[1]);
        for (int i = 0; i < n; i++)
            for (int j = i+1; j < n; j++) {
                dis[i][0][j][0] = func(i, 0, j, 0);
                dis[i][0][j][1] = func(i, 0, j, 1);
                dis[i][1][j][0] = func(i, 1, j, 0);
                dis[i][1][j][1] = func(i, 1, j, 1);
            }
        double l = 0, r = 14500, mid, ans=10000;
        while (l + eps < r) {
            mid = (l + r) / 2;
            if (judge(mid)) {
                ans = mid;
                l = mid + eps;
            } else
                r = mid - eps;
        }
        printf("%.2f\n", ans);
    }
    return 0;
}