洛谷CF590E Birthday(AC自动机)(最小路径可重复点覆盖方案)

题意

给你 n 个字符串,选出最大的一个集合,满足两两之间不是对方的子串。

题解

AC自动机+最小路径可重复点覆盖方案=AC自动机+传递闭包+乱搞
求子串?KMP?这有我这种机智的人才会想到?AC自动机!
AC自动机是用来处理前缀的问题,看起来不适用,但它的fail指针太强大了!可以想象假设现在有一个串,它在trie树中以一条链的形式存储,从这条链中的每个点出去,扩展开来的就是它的一个子串。其实还是一个串后缀与一个串的前缀的匹配,其中后缀指的是一个串的部分,前缀要求是整串,那么就是AC自动机啦~~~
我们给一个串向它的子串连边。
注意到AC自动机只要求出一个即可,后面传递闭包。
因为选了路径开头的一个点,后面一个也不能选,所以接下来求一个最小路径可重复点覆盖方案。
最小路径可重复点覆盖方案就懒得写了,不会看这篇博客

代码

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int END=10000000;
const int MAXN=760,MAXL=10000010;

int n;
string s[MAXN];
bool ma[MAXN][MAXN];

struct Tr{int id,fail,son[2];}tr[MAXL];int root=1,tot=1;//空间??? 

void insert(int id)
{
    int x=root;
    for(int i=0,len=s[id].length();i<len;i++)
    {
        int k=s[id][i]-'a';
        if(!tr[x].son[k]) tr[x].son[k]=++tot;
        x=tr[x].son[k];
    }
    tr[x].id=id;
}

int head,tail,q[MAXL];
void get_fail()
{
    head=0,tail=0;
    for(int k=0,x=root;k<2;k++) if(tr[x].son[k])
    {
        int y=tr[x].son[k];
        tr[y].fail=root;
        q[tail++]=y;if(tail==END) tail=0;
    }
    while(head!=tail)
    {
        int x=q[head++];if(head==END) head=0;
        if(!tr[x].id) tr[x].id=tr[tr[x].fail].id;//debug
        for(int k=0;k<2;k++) if(tr[x].son[k])
        {
            int y=tr[x].son[k],p=tr[x].fail;
            while(p!=root && !tr[p].son[k])p=tr[p].fail;
            if(tr[p].son[k]) tr[y].fail=tr[p].son[k];
            else tr[y].fail=root;
            q[tail++]=y;if(tail==END) tail=0;
        }
    }
}

void solve(int id)
{
    int x=root;
    for(int i=0,len=s[id].length();i<len;i++)
    {
        int k=s[id][i]-'a';
        x=tr[x].son[k];
        if(tr[x].id!=id) ma[id][tr[x].id]=true;//id->tr[x].id
        else ma[id][tr[tr[x].fail].id]=true;
    }
}

int T=0,vis[MAXN];
int match[MAXN];
bool succ[MAXN];
bool dfs(int x)
{
    for(int y=1;y<=n;y++) if(ma[x][y] && vis[y]!=T)
    {
        vis[y]=T;
        if(!match[y] || dfs(match[y])) return match[y]=x,true;
    }
    return false;
}

int li[MAXN];
int main()
{
//     freopen("string.in","r",stdin);
//     freopen("string.out","w",stdout);
    
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        insert(i);
    }
    
    get_fail();
    for(int i=1;i<=n;i++) solve(i);
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) ma[i][j]|=ma[i][k]&ma[k][j];
    
    /*debug
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++) printf("%d",ma[i][j]);
        puts("");
    }
//    */
    
    int ans=n;
    for(int i=1;i<=n;i++)
    {
        T++;
        if(dfs(i)) ans--,succ[i]=true;
    }
    printf("%d\n",ans);
    
    for(int i=1,cnt=0;i<=n;i++) if(!succ[i]) li[++cnt]=i;
    T++;bool modify=true;
    while(modify)
    {
        modify=false;
        for(int i=1;i<=ans;i++)
            for(int j=1;j<=n;j++) if(ma[li[i]][j]) vis[j]=T;
        for(int i=1;i<=ans;i++)
            if(vis[li[i]]==T)
            {
                modify=true;
                while(vis[li[i]]==T) li[i]=match[li[i]];
            }
    }
    for(int i=1;i<=ans;i++) printf("%d ",li[i]);putchar('\n');
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值