LOJ6036 编码 2-SAT、Trie

传送门


每个串只有一个??还只能填0或者1,不难想到2-SAT求解。

一个很暴力的想法是枚举?0或者1,然后对所有可能的前缀连边。这样边数是\(O(n^2)\)的,需要优化。

看到前缀不难想到Trie树。将所有串的所有可能形态填入Trie树中,然后使用前缀后缀优化2-SAT连边的方式优化连边。

具体来说对于每一个串开两个点表示?0还是1,对于Trie树上每一个串的结束节点也开两个点,表示这个点及其所有前缀中是否存在已经选过的串。

连边考虑一些互为前缀的串。设串为\(s_1,s_2,s_3,...,s_k\),第\(i\)个串在Trie树上的节点的\(01\)变量为\(bool[i][0/1]\),第\(i\)个节点对应串的\(01\)变量为\(belong[i][0/1]\)(为了好描述,这里定义的\(belong[i][0/1]\)表示第\(i\)个串填入01之后是否得到当前串,是为\(1\)

那么有边

\(belong[i][1] \rightarrow bool[i][1]\)

\(bool[i][0] \rightarrow bool[i -1][0]\)

\(bool[i][1] \rightarrow bool[i + 1][1]\)

\(bool[i][0] \rightarrow belong[i][0]\)

\(bool[i][1] \rightarrow belong[i + 1][0]\)

这些边可以在建Trie的过程中直接建。记得要建逆否命题的边。然后跑一遍缩点就行了

细节:①开始要将字符串按长度从小到大排序,才可以保证上面方法的正确性;②可能存在某些串相等,建Trie的时候要特别注意。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std;

const int MAXN = 3e6 + 3;
struct Edge{
    int end , upEd;
}Ed[MAXN << 3];
int head[MAXN] , N , cntN = 1 , cntEd;

inline void addEd(int a , int b){
    Ed[++cntEd] = (Edge){b , head[a]};
    head[a] = cntEd;
}

namespace Trie{
    int ch[MAXN][2] , ind[MAXN] , cnt = 1;

    void insert(string s , int bl){
        int cur = 1 , up = 0;
        for(auto c : s){
            if(!ch[cur][c - '0'])
                ch[cur][c - '0'] = ++cnt;
            cur = ch[cur][c - '0'];
            if(ind[cur]) up = ind[cur];
        }
        cntN += 2;
        addEd(bl , cntN); addEd(cntN ^ 1 , bl ^ 1);
        if(up){
            addEd(up , cntN); addEd(cntN ^ 1 , up ^ 1);
            addEd(up , bl ^ 1); addEd(bl , up ^ 1);
        }
        ind[cur] = cntN;
    }
}
using Trie::insert;

int stk[MAXN] , dfn[MAXN] , low[MAXN] , in[MAXN];
int top , ts , cntSCC;
bool vis[MAXN] , ins[MAXN];

void pop(int x){
    ++cntSCC;
    do{
        in[stk[top]] = cntSCC;
        ins[stk[top]] = 0;
    }while(stk[top--] != x);
}

void tarjan(int x , int p){
    vis[x] = ins[x] = 1;
    stk[++top] = x;
    dfn[x] = low[x] = ++ts;
    for(int i = head[x] ; i ; i = Ed[i].upEd){
        if(!vis[Ed[i].end]) tarjan(Ed[i].end , x);
        else if(!ins[Ed[i].end]) continue;
        low[x] = min(low[x] , low[Ed[i].end]);
    }
    if(dfn[x] == low[x]) pop(x);
}

vector < string > str;

bool cmp(string a , string b){return a.size() < b.size();}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    cin >> N;
    for(int i = 1 ; i <= N ; ++i){
        string s;
        cin >> s;
        str.push_back(s);
    }
    sort(str.begin() , str.end() , cmp);
    for(auto t : str){
        cntN += 2;
        int nd = cntN , pos = t.find('?');
        if(pos != string::npos){
            t[pos] = '0';
            insert(t , nd - 1);
            t[pos] = '1';
            insert(t , nd);
        }
        else{
            insert(t , nd);
            addEd(nd - 1 , nd);
        }
    }
    for(int i = 2 ; i <= cntN ; ++i)
        if(!vis[i]) tarjan(i , 0);
    for(int i = 2 ; i <= cntN ; i += 2)
        if(in[i] == in[i + 1])
            return puts("NO") , 0;
    puts("YES");
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10467861.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值