1978: #6036. 「雅礼集训 2017 Day4」编码

题目描述

Bob 最新学习了一下二进制前缀编码的那一套理论。二进制编码是指一个由 n nn 个互不相同的二进制串 s1,s2,…,sn s_1, s_2, \ldots, s_ns1,s2,…,sn 构成的集合。而如果一套编码理论满足,对于任意的 i≠j i \neq ji≠j,si s_isi 不是 sj s_jsj 的前缀,那么我们称它为前缀编码。

Bob 发现了一张上面写有 n nn 行二进制编码的纸,但这张纸年代久远,有些字迹已经模糊不清。幸运的是,每一行至多只会有一个模糊的字符。

Bob 想知道这 n nn 行二进制编码是否有可能是一个前缀编码?

输入

第一行一个整数 n nn,表示编码的大小。
接下来 n nn 行,每行一个由0、1及?组成的字符串。保证每一行至多有一个?。

输出

如果这 n nn 个二进制编码可能是前缀编码,输出YES,否则输出NO。

样例输入 

4
00?
0?00
?1
1?0

样例输出 

YES

提示

本题采用捆绑测试。
你需要通过一个子任务内的所有测试点才能得到该子任务的分数。

子任务分值n nn字符串总长
120≤10 \leq 10≤10≤1000 \leq 1000≤1000
230≤1000 \leq 1000≤1000≤500000 \leq 500000≤500000
350≤500000 \leq 500000≤500000≤500000 \leq 500000≤500000

#include<bits/stdc++.h>
using namespace std;
template<class T>inline void init(T&x){
    x=0;char ch=getchar();bool t=0;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    if(t) x=-x;return;
}
#define TR(a) ((a)<<1|1)
#define FA(a) ((a)<<1)
const int N=5e5+10;
const int MAXN=3e6+10;
int son[MAXN][2];
char S[N];
char *s[N];int len[N],id[N];
int n,cnt=0;
inline bool cmp(int i,int j){return len[i]<len[j];}
struct edge{
    int to,next;
}a[MAXN];
int head[MAXN],cur=0,dfn[MAXN],low[MAXN],bel[MAXN],stk[MAXN],top=0,vis[MAXN],I=0,bcc;
inline void add(int x,int y){a[++cur]=(edge){y,head[x]};head[x]=cur;}
void tarjan(int u){
    dfn[u]=low[u]=++I,stk[++top]=u,vis[u]=1;
    for(int v,i=head[u];i;i=a[i].next){
        v=a[i].to;
        if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
        else if(vis[v]) low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]) {
        int v;++bcc;
        do{v=stk[top--];vis[v]=0;bel[v]=bcc;if(bel[v]==bel[v^1]) {puts("NO"),exit(0);}}while(v!=u);
    }
    return;
}
void Insert(char*s,int len,int Jud){
    int p=0,las=0;
    for(int i=1;i<=len;++i) {
        int c=s[i]-'0';
        las=p;
        if(son[p][c]) p=son[p][c];
        else {
            son[p][c]=++cnt;
            if(p) add(TR(p),TR(cnt)),add(FA(cnt),FA(p));
            p=cnt;
        }
    }
    ++cnt;//新建点以防止出现多个串共用了一个点的情况 , 新建的点是要考虑当前点的!
    add(Jud,FA(p)),add(TR(p),Jud^1);
    add(TR(p),TR(cnt)),add(FA(cnt),FA(p));
    add(Jud,TR(cnt)),add(FA(cnt),Jud^1);
    son[las][(int)(son[las][1]==p)]=cnt;
}
int main()
{
    init(n);int now=1;cnt=n;// n 个串选择什么
    for(int i=1;i<=n;++i){
        scanf("%s",S+now);
        len[i]=strlen(S+now);
        s[i]=&S[now-1];now+=len[i];
        id[i]=i;
    }sort(id+1,id+1+n,cmp);
    for(int i=1;i<=n;++i) {
        int t=id[i];int j=0;
        for(j=1;j<=len[t];++j) {
            if(s[t][j]=='?') {// 有问号
                s[t][j]='0';Insert(s[t],len[t],FA(t));//填 0
                s[t][j]='1';Insert(s[t],len[t],TR(t));//填 1
                break;
            }
        }
        if(j>len[t]) {add(TR(t),FA(t));Insert(s[t],len[t],FA(t));}
    }
    for(int i=0;i<=cnt;++i) if(!dfn[i]) tarjan(i);
    puts("YES");
    return 0;
}

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值