【LOJ6036】编码(2-sat)

【LOJ6036】编码(2-sat)

题面

LOJ

题解

很显然的一个暴力:
枚举每个串中的?是什么,然后把和它有前缀关系的串全部给找出来,不合法的连边处理一下,那么直接跑\(2-sat\)就做完了。
现在的问题就在与不合法的数量可能会很多,所以需要优化这个连边的过程。
显然前缀关系和\(Trie\)树上的节点是相关的,即\(Trie\)树上这个串路径上所代表的所有点都是它的前缀,其子树中的每个串都以当前串为前缀。
首先构建\(Trie\),在往下的过程中对于路径上的所有已知前缀连边,这里肯定不能暴力一个个连,考虑把所有结尾的位置全部挂一条链挂在\(Trie\)树的这个节点上,那么只需要一路连下来就好了。
这样子就处理完了当前串的前缀。
但是当前串加入进来之后还可能会作为后面串的前缀,所以也要类似的挂在\(Trie\)树上。
连边的时候顺便处理好逆否命题的连边,这样子就不用建两次边了。
注意一下实际上这里需要两棵\(Trie\)树,一棵从上往下连,一棵从下往上连。
实际处理的时候把\(Trie\)树也建立正反两个点直接当做逆否命题连边就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; 
#define MAX 3000300
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=2;
inline void Add(int u,int v)
{
    e[cnt]=(Line){v,h[u]};h[u]=cnt++;
    u^=1;v^=1;
    e[cnt]=(Line){u,h[v]};h[v]=cnt++;
}
int dfn[MAX],low[MAX],tim,G[MAX],scc;
int S[MAX],top;bool ins[MAX];
string s[MAX];
void Tarjan(int u)
{
    dfn[u]=low[u]=++tim;ins[u]=true;S[++top]=u;
    for(int i=h[u];i;i=e[i].next)
        if(!dfn[e[i].v])Tarjan(e[i].v),low[u]=min(low[u],low[e[i].v]);
        else if(ins[e[i].v])low[u]=min(low[u],dfn[e[i].v]);
    if(dfn[u]==low[u])
    {
        ++scc;int v;
        do{v=S[top--];ins[v]=false;G[v]=scc;}while(u!=v);
    }
}
int son[MAX][2],tot;
int n,len[MAX],p[MAX];
char ch[MAX];
void Insert(int u,int p)
{
    int x=n+1,lst=0;
    for(int i=0;i<len[u];++i)
    {
        if(!son[x][s[u][i]-48])son[x][s[u][i]-48]=++tot;
        lst=x;x=son[x][s[u][i]-48];Add(p,x<<1);
    }
    int y=++tot;
    Add(p,y<<1|1);Add(y<<1,x<<1);
    son[lst][s[u][len[u]-1]-48]=y;
}
bool cmp(int a,int b){return len[a]<len[b];}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)
    {
        scanf("%s",ch);len[i]=strlen(ch);p[i]=i;
        for(int j=0;j<len[i];++j)s[i]+=ch[j];
    }
    sort(&p[1],&p[n+1],cmp);tot=n+1;
    for(int i=1;i<=n;++i)
    {
        int u=p[i];
        for(int j=0;j<len[u];++j)
            if(s[u][j]=='?')
            {
                s[u][j]=48;Insert(u,u<<1);
                s[u][j]=49;Insert(u,u<<1|1);
                break;
            }
            else if(j==len[u]-1)Insert(u,u<<1),Add(u<<1|1,u<<1);
    }
    for(int i=1;i<=(tot<<1|1);++i)if(!dfn[i])Tarjan(i);
    for(int i=1;i<=tot;++i)if(G[i<<1]==G[i<<1|1]){puts("NO");return 0;}
    puts("YES");
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10459050.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值