peiwenjun's blog 没有知识的荒原

CF590E Birthday 题解

题目描述

给定 \(n\) 个字符串 \(s_i\) ,字符集 \(\{a,b\}\)

选择尽可能多的字符串,要求不存在一个是另一个的子串。

输出最多能选多少个,并构造方案。

数据范围

  • \(1\le n\le 750,\sum|s_i|\le 10^7\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{512MB}\)

分析

由于 \(n\) 很小,所以可以把子串关系建成一张图。

多模匹配,先建 \(\text{ACAM}\)

\(s_i\) 的所有子串为,从 \(\texttt{trie}\) 树上走 \(s_i\) 经过的点出发跳 \(\texttt{fail}\) ,经过的所有其它终止节点。

如果枚举 \(i\) ,直接跳 \(\texttt{fail}\) 时间复杂度 \(\mathcal O(n\cdot\sum|s_i|)\) ,究其原因是跳 \(\texttt{fail}\) 时经过了太多冗余节点。

记录从每个点开始跳 \(\texttt{fail}\) 能跳到的第一个终止节点,可以大幅压缩跳 \(\texttt{fail}\) 的代价。

注意到子串关系其实是一个偏序关系,在 \(\texttt{trie}\) 上走 \(s_i\) ,从 \(i\) 向记录的终止节点连边,可以得到一张 \(\texttt{DAG}\)\(s_j\)\(s_i\) 的子串当且仅当存在 \(i\to j\) 的路径。

接下来问题转化为求 \(\texttt{DAG}\) 的最长反链,传送 P4298

根据 dilworth 定理,最长反链等于最小可重链覆盖,也等于传递闭包后的最小不可重链覆盖。

初始有 \(n\) 条链,每条链只有一个孤立点。对于(传递闭包后的)每条边 \((u,v)\) ,其作用为将 \(u\)\(v\) 所在链拼接起来。每个点作为起点和终点分别至多只能用于 \(1\) 次拼接,每次拼接会让总链数 \(-1\)

拆点 \(u_{in},u_{out}\) ,连边 \((u_{out},v_{in},1)\) ,跑二分图最大匹配,则 \(n-\) 最大匹配数就是答案。

对于二分图,从源点沿着流量非零的边 \(dfs\) ,未标记的左部点和标记的右部点属于最小点覆盖。

最大独立集是最小点覆盖的补集,即标记的左部点和未标记的右部点。

如果 \(u_{in}\)\(u_{out}\) 都在最大独立集中,则 \(u\) 属于原图的最长反链。

关于最后一步的理解:

  • 记最大匹配数为 \(m\) ,则最小点覆盖为 \(m\) ,最大独立集为 \(2n-m\) ,因此恰好会选出 \(n-m\) 个点加入最长反链。
  • 由于这些点属于独立集,因此(传递闭包后的)原图不存在 \(u\to v\) 的边,符合反链的定义。

时间复杂度 \(\mathcal O(\sum|s_i|+n^3)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1505,maxm=6e5,maxv=1e7+5,inf=1e9;
int n,tot;
int pos[maxn];
int ch[maxv][2],fa[maxv],lst[maxv],fail[maxv];
char s[maxv];
bool g[maxn][maxn];
namespace flow
{
    int s,t,tot=1;
    int head[maxn],to[maxm],f[maxm],nxt[maxm];
    int d[maxn];
    bool vis[maxn];
    void addedge(int u,int v,int w)
    {
        nxt[++tot]=head[u],to[tot]=v,f[tot]=w,head[u]=tot;
        nxt[++tot]=head[v],to[tot]=u,f[tot]=0,head[v]=tot;
    }
    bool bfs()
    {
        queue<int> q;
        memset(d,0,sizeof(d));
        d[s]=1,q.push(s);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=nxt[i])
            {
                int v=to[i];
                if(f[i]&&!d[v])
                {
                    d[v]=d[u]+1,q.push(v);
                    if(v==t) return true;
                }
            }
        }
        return false;
    }
    int dfs(int u,int limit)
    {
        if(u==t) return limit;
        int flow=0;
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(!f[i]||d[v]!=d[u]+1) continue;
            int k=dfs(v,min(f[i],limit-flow));
            if(!k) d[v]=0;
            f[i]-=k,f[i^1]+=k,flow+=k;
            if(flow==limit) break;
        }
        return flow;
    }
    int dinic()
    {
        int flow=0;
        while(bfs()) flow+=dfs(s,inf);
        return flow;
    }
    void dfs2(int u)
    {
        if(vis[u]) return ;
        vis[u]=true;
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(f[i]) dfs2(v);
        }
    }
}
using flow::addedge;
using flow::vis;
void insert(char *s,int num)
{
    int n=strlen(s+1),p=0;
    for(int i=1;i<=n;i++)
    {
        int k=s[i]-'a';
        if(!ch[p][k]) ch[p][k]=++tot,fa[tot]=p;
        p=ch[p][k];
    }
    lst[p]=num,pos[num]=p;
}
void getfail()
{
    queue<int> q;
    for(int i=0;i<=1;i++)
        if(ch[0][i])
        {
            fail[ch[0][i]]=0;
            q.push(ch[0][i]);
        }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(!lst[u]) lst[u]=lst[fail[u]];
        for(int i=0;i<=1;i++)
            if(ch[u][i])
            {
                fail[ch[u][i]]=ch[fail[u]][i];
                q.push(ch[u][i]);
            }
            else ch[u][i]=ch[fail[u]][i];
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%s",s+1),insert(s,i);
    getfail();
    flow::s=0,flow::t=2*n+1;
    for(int i=1;i<=n;i++)
    {
        addedge(flow::s,i,1),addedge(n+i,flow::t,1);
        for(int j=pos[i];j;j=fa[j])
        {
            if(j==pos[i]) g[lst[fail[j]]][i]=true;
            else g[lst[j]][i]=true;
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                g[i][j]|=g[i][k]&g[k][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i!=j&&g[i][j])
                addedge(i,n+j,1);
    printf("%d\n",n-flow::dinic());
    flow::dfs2(flow::s);
    for(int i=1;i<=n;i++) if(vis[i]&&!vis[n+i]) printf("%d ",i);
    putchar('\n');
    return 0;
}

posted on 2022-08-08 19:59  peiwenjun  阅读(7)  评论(0)    收藏  举报

导航