SPOJ SOPARADE(JZOJ 4696 第四次忍者大战) 根据条件构图跑2-SAT

题目大意

现在有 N 个位置,每个位置要填入一个1 4 的整数,要求亮亮间的差要大于等于二。并且现在有M条约束,每条约束规定了 k 个位置B1,B2...Bk上的数两两不同。问是否有合法的填法。

N,M105

解题思路

我们一步步从题目的约束中挖掘性质。

首先,相邻的两个数的差的大于等于2,那么1的傍边只能是3,4。2的旁边只能是4。3的旁边只能是1。4的旁边只能是1,2。我们发现1,2和3,4的填入肯定是交错的,就是说奇数位一定是1,2,偶数位一定是3,4(反过来也行,没什么区别)。那么对于一个点就只有两个选择,那么这就非常像2-SAT了。

那么我们就按2-SAT的方式连边,比如奇数位的2就一定要像右边的偶数位的4连一条边,因为选了2就只能在旁边选4。类似的我们就解决了相邻的性质。对于 M 条约数也很简单,我们同样要分奇数位和偶数位讨论,因为他们之间不可能相同。那么我们只需在奇偶性相同的位置连一条交叉的双向边(比如3连到4,4连到3)。最后跑一次2-SAT判是否有解就可以了。

提示:解决相邻的约束时,只用从前面往后面连边。因为我们做2-SAT时是默认了做完了前i个位置,如果再对前面造成约束就会有问题。

程序

十分丑的代码。。。

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 2e5 + 5;

int N, M, c, Test, S[MAXN];
int tot, Last[MAXN], Next[MAXN * 40], Go[MAXN * 40];
bool mark[MAXN];

void Link(int a, int b) {
    Next[++ tot] = Last[a], Last[a] = tot, Go[tot] = b;
}

void Read(int &Now) {
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    Now = 0;
    while (ch >= '0' && ch <= '9') Now = Now * 10 + ch - '0', ch = getchar();
}

bool Dfs(int x) {
    int y = (x & 1) ? (x + 1) : (x - 1);
    if (mark[y]) return 0;
    if (mark[x]) return 1;
    mark[x] = 1;
    S[c ++] = x;
    for (int p = Last[x]; p; p = Next[p])
        if (!Dfs(Go[p])) return 0;
    return 1;
}

bool Solve() {
    memset(mark, 0, sizeof mark);
    memset(Last, 0, sizeof Last);
    tot = 0;
    Read(N), Read(M);
    for (int i = 1; i < N; i ++) {
        int a = i, b = i + 1;
        Link(a * 2, b * 2 - 1);
    }
    for (int i = 1; i <= M; i ++) {
        int Num;
        Read(Num);
        if (Num > 4) return 0;
        static int _0[3], _1[3];
        _0[0] = _1[0] = 0;
        for (int j = 0; j < Num; j ++) {
            int Now;
            Read(Now);
            if (Now & 1) _0[++ _0[0]] = Now; else _1[++ _1[0]] = Now;
        }
        if (_0[0] > 2 || _1[0] > 2) return 0;
        if (_0[0] > 1) {
            Link(_0[1] * 2, _0[2] * 2 - 1);
            Link(_0[1] * 2 - 1, _0[2] * 2);
            Link(_0[2] * 2, _0[1] * 2 - 1);
            Link(_0[2] * 2 - 1, _0[1] * 2);
        };
        if (_1[0] > 1) {
            Link(_1[1] * 2, _1[2] * 2 - 1);
            Link(_1[1] * 2 - 1, _1[2] * 2);
            Link(_1[2] * 2, _1[1] * 2 - 1);
            Link(_1[2] * 2 - 1, _1[1] * 2);
        }
    }

    for (int i = 1; i <= N * 2; i += 2) {
        if (!mark[i] && !mark[i + 1]) {
            c = 0;
            if (!Dfs(i)) {
                while (c > 0) mark[S[-- c]] = 0;
                if (!Dfs(i + 1)) return 0;
            }
        }
    }
    return 1;
}

int main() {
    int Test;
    scanf("%d", &Test);
    for (int i = 1; i <= Test; i ++) {
        if (Solve()) printf("approved\n"); else
            printf("rejected\n");
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值