[BZOJ2754][SCOI2012]喵星球上的点名(AC自动机+fail树+容斥+lca)

15 篇文章 0 订阅
9 篇文章 0 订阅

题目:

我是超链接

题解:

第一问是求每个短串出现在多少个长串里。有了上道题目的经验我们可以求一个短串里有多少长串出现,即以短串end节点为根的fail树中有多少个不同的长串节点。加入点名串,求fail树,枚举每一个长串lca容斥。

这里一开始我匹配的姿势似乎不太对劲,每个点匹配到的应该是在AC自动机里最深的点。

第二问是求每个长串中出现了多少短串,在fail树上的表示方法也就是长串中的每个节点到根节点经过多少短串的end节点,我们一样可以用差分解决,具体就是在每个end节点的子树+1,然后长串相邻两个节点的lca-1,对于长串每个节点统计前缀和就好咯

这里因为最大的数字是10000,我们要用map建立AC自动机,但是这样的问题就是如何建立fail树,我们总不能从0~10000枚举啊。可以在建立AC自动机的时候添加边将节点连接起来,这样每次遍历的时候只要前向星就好了

代码:

#include <map>
#include <queue>
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=200005;
const int sz=18;
int l,tot,nxt[N],point[N],v[N],cnt,num,in[N],out[N],h[N],mi[sz+5],f[N][sz+5],is_end[N],fail[N],cx[N],cd[N],ans[N],nc[N],vis[N],yx[N];
map<int,int>ch[N],name[2][N];int tot1,nxt1[N],point1[N],v1[N],c1[N];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
void addline1(int x,int y,int z){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y; c1[tot1]=z;}
void insert(int id)
{
    scanf("%d",&l);
    int now=0,x;
    for (int i=1;i<=l;i++)
    {
        scanf("%d",&x);
        if (!ch[now][x]) ch[now][x]=++cnt,addline1(now,cnt,x);
        now=ch[now][x];
    }
    is_end[id]=now;
    yx[now]++;
}
void sp()
{
    queue<int>q;
    for (int i=point1[0];i;i=nxt1[i]) q.push(v1[i]);
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        for (int i=point1[now];i;i=nxt1[i]) 
        {
            int k=fail[now];
            while (!ch[k][c1[i]] && k) k=fail[k];
            fail[v1[i]]=ch[k][c1[i]];  
            q.push(v1[i]);
         }
    }
    for (int i=1;i<=cnt;i++) addline(fail[i],i);
}
void dfs(int x)
{
    in[x]=++num;
    for (int i=1;i<sz;i++)
      if (h[x]<mi[i]) break;
      else f[x][i]=f[f[x][i-1]][i-1];
    for (int i=point[x];i;i=nxt[i])
    {
        f[v[i]][0]=x;
        h[v[i]]=h[x]+1;
        dfs(v[i]);
    }
    out[x]=num;
}
int lca(int x,int y)
{
    if (h[x]<h[y]) swap(x,y);
    int k=h[x]-h[y];
    for (int i=0;i<sz;i++)
      if (k>>i&1) x=f[x][i];
    if (x==y) return x;
    for (int i=sz-1;i>=0;i--)
      if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void add(int *c,int loc,int v){for (int i=loc;i<=num;i+=i&(-i)) c[i]+=v;}
int qurry(int *c,int loc)
{
    int ans=0;
    for (int i=loc;i>=1;i-=i&(-i)) ans+=c[i];
    return ans;
}
int cmp(int a,int b){return in[a]<in[b];}
void ac(int id)
{
    int now=0,lj=0;
    for (int i=1;i<=name[0][id][0];i++)
    {
        while (!ch[now][name[0][id][i]] && now) now=fail[now];
        if (!ch[now][name[0][id][i]]) now=0;
        else now=ch[now][name[0][id][i]];
        if (vis[now]!=id) nc[++lj]=now;
        vis[now]=id;
    }
    now=0;
    for (int i=1;i<=name[1][id][0];i++)
    {
        while (!ch[now][name[1][id][i]] && now) now=fail[now];
        if (!ch[now][name[1][id][i]]) now=0;
        else now=ch[now][name[1][id][i]];
        if (vis[now]!=id) nc[++lj]=now;
        vis[now]=id;
    }
    sort(nc+1,nc+lj+1,cmp);
    add(cd,in[nc[1]],1);ans[id]+=qurry(cx,in[nc[1]]);
    for (int i=2;i<=lj;i++)
    {
        add(cd,in[nc[i]],1);
        add(cd,in[lca(nc[i],nc[i-1])],-1);
        ans[id]+=qurry(cx,in[nc[i]]);
        ans[id]-=qurry(cx,in[lca(nc[i],nc[i-1])]);
    }
}
int main()
{
    mi[0]=1;for (int i=1;i<sz;i++) mi[i]=mi[i-1]*2;
    int n,m;scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) 
    {
        scanf("%d",&name[0][i][0]);
        for (int j=1;j<=name[0][i][0];j++) scanf("%d",&name[0][i][j]);
        scanf("%d",&name[1][i][0]);
        for (int j=1;j<=name[1][i][0];j++) scanf("%d",&name[1][i][j]);
    }
    for (int i=1;i<=m;i++) insert(i); 
    sp(); h[0]=1; dfs(0);
    for (int i=1;i<=m;i++) add(cx,in[is_end[i]],1),add(cx,out[is_end[i]]+1,-1);
    for (int i=1;i<=n;i++) ac(i);
    for (int i=1;i<=m;i++) printf("%d\n",qurry(cd,out[is_end[i]])-qurry(cd,in[is_end[i]]-1));
    for (int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d",ans[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值