JZOJ4696.第四次忍者大战

原题:Spoj-SOPARADE

题目大意

一个有 n 个元素的序列a每个元素在 [1..4] 要求相邻两数之差的绝对值大于等于2。
给定 m 个约束条件,每个条件k个数,表示这 k 个位置的元素各不相同。
询问是否有合法的序列满足条件。

Data Constraint
n,m105

题解

一个结论:奇数位置只填1/2,偶数只填3/4(或者反过来),因为要求相邻两数之差的绝对值大于等于2。
可以注意到,每个约束条件里最多两个奇数和两个偶数。然后就可以将约束条件转换成2-SAT的形式了。

2-SAT

现由一些元素可以取真或假,给出若干个约束条件,要求出每个元素的取值满足所有条件。
将问题转化成图模型,先把每个点 i 拆成两个i n+i ,分别表示真或假。
假如现在有一个条件,i为真或者j为真,那么我们就这样连两条单向边:

  • n+i -> j
  • n+j-> i

如果i取假,那么 j 必取真,反之亦然。可以将一条有向边理解成“推导出”的意思。
将所有约束条件都转化成有向边之后我们就可以在图上染色来求出每个元素的取值。这里讲一种蓝书上的方法:
每次找到一个没有标记过的元素i,然后我们假定它选真,即从 i 开始标记经过的点,如果出现某个点被标记两次就说明出现了矛盾,我们就把标记操作退回,再从n+i开始标记。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 100000 + 10
#define M 1000000 + 10

int Node[2*M] , Next[2*M] , Head[2*N] , tot ;

bool flag , Mark[2*N] ;
int a[N] , b[N] , S[N] ;
int T , n , m , top ;

int Read() {
    int num = 0 ;
    char c = getchar() ;
    while ( c < '0' || c > '9' ) c = getchar() ;
    while ( c >= '0' && c <= '9' ) num = num * 10 + c - '0' , c = getchar() ;
    return num ;
}

void link( int u , int v ) {
    Node[++tot] = v ;
    Next[tot] = Head[u] ;
    Head[u] = tot ;
}

void MakeGraph() {
    if ( a[0] == 2 ) {
        link( a[1] , n + a[2] ) ;
        link( n + a[2] , a[1] ) ;
        link( n + a[1] , a[2] ) ;
        link( a[2] , n + a[1] ) ;
    }
    if ( b[0] == 2 ) {
        link( b[1] , n + b[2] ) ;
        link( n + b[2] , b[1] ) ;
        link( n + b[1] , b[2] ) ;
        link( b[2] , n + b[1] ) ;
    }
}

int opp( int x ) {
    return x <= n ? x + n : x - n ;
}

bool DFS( int x ) {
    if ( Mark[opp(x)] ) return 0 ;
    if ( Mark[x] ) return 1 ;
    S[++top] = x ;
    Mark[x] = 1 ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( !DFS(Node[p]) ) return 0 ;
    }
    return 1 ;
}

int main() {
    freopen( "war.in" , "r" , stdin ) ;
    freopen( "war.out" , "w" , stdout ) ;
    scanf( "%d" , &T ) ;
    while ( T -- ) {
        tot = 0 ;
        flag = 1 ;
        memset( Head , 0 , sizeof(Head) ) ;
        memset( Mark , 0 , sizeof(Mark) ) ;
        n = Read() , m = Read() ;
        for (int i = 1 ; i <= n ; i += 2 ) {
            int node = i + 1 ;
            if ( node <= n ) {
                link( node , i ) ;
                link( n + i , n + node ) ;
            }
            node = i - 1 ;
            if ( node >= 1 ) {
                link( node , i ) ;
                link( n + i , n + node ) ;
            }
        }
        for (int i = 1 ; i <= m ; i ++ ) {
            int k = Read() ;
            a[0] = b[0] = 0 ;
            for (int i = 1 ; i <= k ; i ++ ) {
                int x = Read() ;
                if ( x % 2 ) b[++b[0]] = x ;
                else a[++a[0]] = x ;
            }
            if ( a[0] > 2 || b[0] > 2 ) flag = 0 ;
            MakeGraph() ;
        }
        for (int i = 1 ; i <= n && flag ; i ++ ) {
            if ( Mark[i] || Mark[i+n] ) continue ;
            top = 0 ;
            if ( !DFS(i) ) {
                while ( top ) Mark[S[top]] = 0 , top -- ;
                if ( !DFS(n+i) ) { flag = 0 ; break ; }
            }
        }
        if ( !flag ) printf( "rejected\n" ) ;
        else printf( "approved\n" ) ;
    }
    return 0 ;
}

以上.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值