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

题意

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

题解

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

代码

#include
#include
#include
#include
#include
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();itr[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;
}

 

你可能感兴趣的:(刷题之路,AC自动机,传递闭包,二分图匹配)