JZOJ5010. 编码

题目大意

给定 n 个01串,有一些串中有一个位置未确定。现在要判断,能否找到一种方案,使得任意一个串都不是其他任一串的前缀。

Data Constraint
n,500000

题解

考虑用Trie建图,跑2-SAT
将所有可能的串加入一个Trie里,每个点向它根路径上经过的点的另一个连边,表示这两种不能同时选。
然后Tarjan判一下就好了。

时间复杂度: O(n)

SRC

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

#define N 5000000 + 10
#define M 500000 + 10
struct Trie {
    int Son[2] , fa ;
    int In , Out , use ;
} T[2*M] ;

vector < int > P[2*M] ;

char S[N] ;

bool ok = 1 ;
bool vis[N] , flag[N] ;
int Node[5*N] , Next[5*N] , Head[N] , tot ;
int DFN[N] , LOW[N] , Sta[N] , Bel[N] , Time ;
int from[N] , Pre[N] , Suf[N] ;
int n , Cnt , Col , Amo , Num = 1 ;

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

int Rev( int x ) {
    if ( x > 2 * n ) return 0 ;
    return x > n ? x - n : x + n ;
}

void Insert( int len , int ID ) {
    int now = 1 ;
    for (int i = 1 ; i <= len ; i ++ ) {
        int c = S[i] - '0' ;
        if ( !T[now].Son[c] ) {
            T[now].Son[c] = ++ Num ;
            T[Num].fa = now ;
        }
        now = T[now].Son[c] ;
    }
    if ( !T[now].use ) T[now].use = ++ Amo ;
    P[T[now].use].push_back( ID ) ;
    if ( !T[now].In ) T[now].In = ++ Cnt ;
    if ( !T[now].Out ) T[now].Out = ++ Cnt ;
    link( T[now].In , Rev(ID) ) ;
    link( ID , T[now].Out ) ;
    from[ID] = now ;
}

void Build( int st ) {
    if ( vis[st] ) return ;
    vis[st] = 1 ;
    int siz = P[T[st].use].size() ;
    Pre[0] = Suf[siz+1] = 0 ;
    for (int i = 1 ; i <= siz ; i ++ ) {
        int now = Rev(P[T[st].use][i-1]) ;
        Pre[i] = ++ Cnt ;
        if ( Pre[i-1] > 0 ) link( Pre[i] , Pre[i-1] ) ;
        link( Pre[i] , now ) ;
    }
    for (int i = siz ; i >= 1 ; i -- ) {
        int now = Rev(P[T[st].use][i-1]) ;
        Suf[i] = ++ Cnt ;
        if ( Suf[i+1] > 0 ) link( Suf[i] , Suf[i+1] ) ;
        link( Suf[i] , now ) ;
    }
    for (int i = 1 ; i <= siz ; i ++ ) {
        int now = P[T[st].use][i-1] ;
        if ( Pre[i-1] > 0 ) link( now , Pre[i-1] ) ;
        if ( Suf[i+1] ) link( now , Suf[i+1] ) ;
    }
    int last = st ;
    for (int x = T[st].fa ; x != 1 ; x = T[x].fa ) {
        if ( T[x].In == 0 ) continue ;
        link( T[last].Out , T[x].In ) ;
        link( T[x].Out , T[last].In ) ;
    }
}

void Tarjan( int x ) {
    Sta[++Sta[0]] = x ;
    flag[x] = 1 ;
    DFN[x] = LOW[x] = ++ Time ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( !DFN[Node[p]] ) {
            Tarjan( Node[p] ) ;
            LOW[x] = min( LOW[x] , LOW[Node[p]] ) ;
        } else if ( flag[Node[p]] ) LOW[x] = min( LOW[x] , DFN[Node[p]] ) ;
        if ( !ok ) return ;
    }
    if ( DFN[x] == LOW[x] ) {
        Col ++ ;
        while ( Sta[0] ) {
            int now = Sta[Sta[0]] ;
            Bel[now] = Col ;
            flag[now] = 0 ;
            Sta[0] -- ;
            if ( Bel[now] == Bel[Rev(now)] ) { ok = 0 ; return ; }
            if ( now == x ) break ;
        }
    }
}

bool Check() {
    for (int i = 1 ; i <= 2 * n ; i ++ ) {
        if ( !DFN[i] ) Tarjan( i ) ;
        if ( !ok ) return 0 ;
    }
    return 1 ;
}

int main() {
    freopen( "code.in" , "r" , stdin ) ;
    freopen( "code.out" , "w" , stdout ) ;
    scanf( "%d" , &n ) ;
    Cnt = 2 * n ;
    for (int i = 1 ; i <= n ; i ++ ) {
        scanf( "%s" , S + 1 ) ;
        int len = strlen( S + 1 ) , wz = 0 ;
        for (int j = 1 ; j <= len ; j ++ ) {
            if ( S[j] == '?' ) wz = j ;
        }
        if ( !wz ) Insert( len , i ) , link( n + i , i ) ;
        else {
            from[i] = from[n+i] = 1 ;
            S[wz] = '0' ;
            Insert( len , i ) ;
            S[wz] = '1' ;
            Insert( len , n + i ) ;
        }
    }
    for (int i = 1 ; i <= 2 * n ; i ++ ) {
        if ( !from[i] ) continue ;
        Build( from[i] ) ;
    }
    if ( Check() ) printf( "YES\n" ) ;
    else printf( "NO\n" ) ;
    return 0 ;
}

以上.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值