[NOI2015]品酒大会

后缀自动机万岁!

一、题目

点此看题

二、解法

把题目的问题稍作转化,我们可以求出有多少对后缀满足 l c p ≥ i lcp\geq i lcpi和求出这些对中乘积的最大值,我们只需要求出 l c p = i lcp=i lcp=i的值然后后缀和统计即可。

建出后缀自动机(由于权值应该存在结束的地方,所以我们需要把输入的串反过来建),考虑在后缀自动机上 d p dp dp,两个后缀的 l c p lcp lcp就是 p a r e n t parent parent树上的 l c a lca lca,我们维护子树标记点数量 s i z siz siz,最大值和次大值,最小值和次小值,因为权值中有负数,所以都要考虑。

然后在 d p dp dp的时候就把答案给存下来, d p dp dp过程就不讲了,如果 s i z < 2 siz<2 siz<2的话就直接退出来。

时间复杂度 O ( n ) O(n) O(n),还有一些易错点(标记了注释)和细节,可以参考我的代码。

#include <cstdio>
#include <cstring>
#include <iostream>
#define int long long
#define inf (1e9+7)
using namespace std;
const int M = 600005;
int read()
{
 int x=0,flag=1;char c;
 while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
 while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
 return x*flag;
}
int n,w[M];char s[M];
struct node
{
    int len,fa,ch[26];
    node() {memset(ch,0,sizeof ch);len=fa=0;}
};
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V) , next(N) {}
};
struct automaton
{
    int cnt,last,mx[M],mx2[M],mi[M],mi2[M];node a[M];
    int tot,siz[M],sum[M],ans[M],f[M];edge e[2*M];
    automaton() {cnt=last=1;}
    void add(int c,int w)
    {
        int p=last,np=last=++cnt;siz[np]=1;
        mx[np]=mi[np]=w;mx2[np]=-inf;mi2[np]=inf;
        a[np].len=a[p].len+1;
        for(;p && !a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;
        if(!p) a[np].fa=1;
        else
        {
            int q=a[p].ch[c];
            if(a[q].len==a[p].len+1) a[np].fa=q;
            else
            {
                int nq=++cnt;mx[nq]=mx2[nq]=-inf;mi[nq]=mi[nq]=inf;
                //一定要赋初值,卡了我好久,注意注意!!
                a[nq]=a[q];a[nq].len=a[p].len+1;
                a[q].fa=a[np].fa=nq;
                for(;p && a[p].ch[c]==q;p=a[p].fa) a[p].ch[c]=nq;
            }
        }
    }
    void link(int u,int v)
    {
        e[++tot]=edge(v,f[u]),f[u]=tot;
        e[++tot]=edge(u,f[v]),f[v]=tot;
    }
    void upx(int u,int v)
    {
        if(mx[u]<v)
        {
            mx2[u]=mx[u];
            mx[u]=v;
        }
        else if(mx2[u]<v)
            mx2[u]=v;
    }
    void upn(int u,int v)
    {
        if(mi[u]>v)
        {
            mi2[u]=mi[u];
            mi[u]=v;
        }
        else if(mi2[u]>v)
            mi2[u]=v;
    }
    void dfs(int u)
    {
        int len=a[u].len,fa=a[u].fa,s=siz[u];
        for(int i=f[u];i;i=e[i].next)
        {
            int v=e[i].v;
            if(v==fa) continue;
            dfs(v);
            upx(u,mx[v]);upx(u,mx2[v]);
            upn(u,mi[v]);upn(u,mi2[v]);
            s+=siz[v];
        }
        if(s<2) {siz[u]=s;return ;}//赋值一下严谨些吧qwq,虽然没有卡这个点
        ans[len]=max(ans[len],max(mi[u]*mi2[u],mx[u]*mx2[u]));
        for(int i=f[u];i;i=e[i].next)
        {
            int v=e[i].v;
            if(v==fa) continue;
            sum[len]+=siz[u]*siz[v];
            siz[u]+=siz[v];
        }
    }
    void solve()
    {
        memset(ans,-63,sizeof ans);
        mx[1]=mx2[1]=-inf;mi[1]=mi2[1]=inf;
        for(int i=2;i<=cnt;i++) link(i,a[i].fa);
        dfs(1);
        for(int i=n-1;i>=0;i--)
            sum[i]+=sum[i+1],ans[i]=max(ans[i],ans[i+1]);
        for(int i=0;i<n;i++)
            printf("%lld %lld\n",sum[i],!sum[i]?0ll:ans[i]);
    }
}Sam;
signed main()
{
    n=read();
    scanf("%s",s);
    for(int i=0;i<n;i++) w[i]=read();
    for(int i=n-1;i>=0;i--) Sam.add(s[i]-'a',w[i]);
    //倒着建Sam
    Sam.solve();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值