【UOJ131/NOI2015D2T2-品酒大会】sam求后缀树

题目链接:http://uoj.ac/problem/131

题意:给出一个字符串,第i个字符对应的值为a[i], 对于i∈[0,n),求最长公共前缀大于等于i的字串对个数,并求这些字符串对开头对应值相乘最大值。n=3*10^5

题解:

学了个厉害的东西啊。。。

正解好像是sa+并查集(合并height)

然而我学了个用sam的做法。。

对于第一问:

首先我们要知道,建立后缀自动机之后,parent树就是逆序串的后缀树。

why?看这个博客好了:http://z55250825.blog.163.com/blog/static/15023080920144542541495/

直接逆序建后缀自动机,

因为对于现在parent树而言,任意两点的LCP等于两点在树上的LCA的step(step就是sam里的那个step。。一开始没想清楚还以为是parent-tree上的深度。。于是WA了。。)

这是转化成一个简单的树形dp了:按逆拓扑序更新(从孩子到parent),对于当前点x,看它是多少对点对的lcp。

假设有四个孩子,孩子的点数(就是这棵子树上有多少个点)分别为s1,s2,s3,s4

cnt[x]=1*(s1+s2+s3+s4)(这是x到x的孩子)  +  (s1+s2+s3)*s4  + (s1+s2)*s3  + s1*s2

那我们每遍历一个孩子y,就sum[x]+=sum[y],对于一个新的孩子yy,cnt[x]+=sum[x]*sum[yy];

对于第二问:

对于当前的parent树而言,等价于求parent树上两个叶节点乘积的最大值。

又因为考虑到ai可能是负数,所以我们只需要记录最大值,次大值,最小值,次小值就可以了。

参考题解:http://www.cnblogs.com/joyouth/p/5366396.html

注意很多细节。。

sam真的超厉害。。可以直接转化成后缀树和后缀数组。。

ORZ。。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

typedef long long LL;
const int N=2*3*100010;
const LL INF=1LL<<62;
int sl,cl,tot,last,c[N],in[N],first[N],step[N],pre[N],son[N][30];
LL w[N],cnt[N],ans[N],mx[N],smx[N],mn[N],smn[N],sum[N];
char s[N];
bool vis[N];
queue<int> Q;

LL maxx(LL x,LL y){return x>y ? x:y;}
LL minn(LL x,LL y){return x<y ? x:y;}
void gmax(LL &x,LL y){x=maxx(x,y);}
void gmin(LL &x,LL y){x=minn(x,y);}

int add_node(int x)
{
    step[++tot]=x;
    return tot;
}

void clear()
{
    memset(son,0,sizeof(son));
    memset(pre,0,sizeof(pre));
    memset(step,0,sizeof(step));
    memset(in,0,sizeof(in));
    // memset(cnt,0,sizeof(cnt));
    memset(sum,0,sizeof(sum));
    tot=0;add_node(0);last=1;
}

void extend(int ch)
{
    int p=last,np=add_node(step[p]+1);
    while(p && !son[p][ch])
    {
        son[p][ch]=np;
        in[np]++;
        p=pre[p];
    }
    if(!p) pre[np]=1;
    else
    {
        int q=son[p][ch];
        if(step[q]==step[p]+1) pre[np]=q;
        else
        {
            int nq=add_node(step[p]+1);
            for(int i=1;i<=26;i++)
                if(son[q][i]) son[nq][i]=son[q][i],in[son[q][i]]++;
            pre[nq]=pre[q];
            pre[np]=pre[q]=nq;
            while(p && son[p][ch]==q) in[q]--,in[nq]++,son[p][ch]=nq,p=pre[p];
        }
    }
    last=np;
}

void get_tp()
{
    while(!Q.empty()) Q.pop();
    memset(vis,0,sizeof(vis));
    Q.push(1);vis[1]=1;cl=0;
    while(!Q.empty())
    {
        int x=Q.front();c[++cl]=x;vis[x]=0;Q.pop();
        for(int i=1;i<=26;i++)
        {
            int y=son[x][i];
            if(!y) continue;
            in[y]--;
            if(!in[y] && !vis[y]) vis[y]=1,Q.push(y);
        }
    }
}

int main()
{
    freopen("a.in","r",stdin);
    int x,y,ch;
    scanf("%d",&sl);
    scanf("%s",s+1);
    for(int i=1;i<=sl;i++) scanf("%lld",&w[i]);

    clear();
    for(int i=sl;i>=1;i--) extend(s[i]-‘a‘+1);
    get_tp();

    for(int i=1;i<=tot;i++) mx[i]=-INF,smx[i]=-INF,mn[i]=INF,smn[i]=INF;
    x=1;
    for(int i=sl;i>=1;i--)
    {
        ch=s[i]-‘a‘+1;
        x=son[x][ch];
        mx[x]=mn[x]=w[i];
        sum[x]++;
    }

    LL tmp;
    memset(cnt,0,sizeof(cnt));
    for(int i=0;i<=sl;i++) ans[i]=-INF;
    for(int i=cl;i>=1;i--)
    {
        y=c[i],x=pre[y];
        tmp=-INF;
        if(smx[y]>-INF) gmax(tmp,mx[y]*smx[y]);
        if(smn[y]<INF)  gmax(tmp,mn[y]*smn[y]);
        gmax(ans[step[y]],tmp);
        cnt[step[x]]+=sum[x]*sum[y];
        sum[x]+=sum[y];

        if(mx[y]>=mx[x]) smx[x]=mx[x],mx[x]=mx[y];//debug >=
        else if(mx[y]>smx[x]) smx[x]=mx[y];
        if(mn[y]<=mn[x]) smn[x]=mn[x],mn[x]=mn[y];//debug <=
        else if(mn[y]<smn[x]) smn[x]=mn[y];
    }
    // for(int i=0;i<sl;i++) printf("x = %d  cnt = %lld  ans = %lld\n",i,cnt[i],ans[i]);
    for(int i=sl-1;i>=0;i--)
    {

        cnt[i]+=cnt[i+1];
        gmax(ans[i],ans[i+1]);
    }
    for(int i=0;i<sl;i++)
    {
        if(!cnt[i]) ans[i]=0;
        printf("%lld %lld\n",cnt[i],ans[i]);
    }
    return 0;
}
时间: 2024-10-04 22:31:24

【UOJ131/NOI2015D2T2-品酒大会】sam求后缀树的相关文章

【NOI2015】品酒大会 SAM

论没有看完题目的危害,以及预处理不做号的危害.两个小时过去了………………………………………… 不难.把字符串逆序建后缀自动机,利用fail边得到后缀树.(理解上的,实际上没有在树上跑.) 由于后缀树上每一个叶子节点所代表的串是原串的后缀.现在逆序之后,就变成了前缀.那 因为一个节点x的sz数组表示这个节点所代表的所有的串都出现了sz[x]次.而且该串所代表的字符串长度为 dep[fa[x]] + 1 ~ dep[x] 所以理论上考虑一个节点对答案的贡献是是要对 dep[fa[x]+1] ~ de

bzoj4199:NOI2015D2T2品酒大会(SAM版)

SAM感觉写起来比SA更直观(?) #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> #define ll long long #define N 600005 using namespace std; inline int read(){ int ret=0

【uoj131】 NOI2015—品酒大会

http://uoj.ac/problem/131 (题目链接) 题意 给出一个字符串,每个后缀有一个权值${a_i}$,这些后缀两两之间存在公共前缀.问能够组成长度从0~n-1的公共前缀的后缀的方案数以及他们权值的最大乘积. Solution 听LCF说这是水题,就来做了.. lyp学长说SAM构出来后就两个东西:在自动机上dp,在后缀树上搞事情. 于是我们很容易想到在后缀树上dp(感觉在自动机上dp没什么卵用),于是我们把串反过来见后缀自动机,然后建出后缀树,在上面树形dp就可以了. 假设当

【BZOJ4199】[Noi2015]品酒大会 后缀数组+并查集

[BZOJ4199][Noi2015]品酒大会 题面:http://www.lydsy.com/JudgeOnline/wttl/thread.php?tid=2144 题解:听说能用SAM?SA默默水过~ 本题的实现还是非常简单的,先求出height数组,然后两杯酒'r'相似就等价于二者中间的height都>=r,于是我们将height排序,从大到小扔进去,那么所有连续的区间中的点对就都是相似的了.维护连续区间可以用并查集.统计点对个数需要维护size,统计最大乘积需要维护最(次)大(小)值,

【BZOJ-4199】品酒大会 后缀数组 + 并查集合并集合

4199: [Noi2015]品酒大会 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 436  Solved: 243[Submit][Status][Discuss] Description 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加. 在大会的晚餐上,调酒师 Rainbow 调制了 nn 杯鸡尾酒.这 nn 杯鸡尾酒排成一行,其中第 i

关于广义后缀树(多串SAM)的总结

之前我们给的SAM的例题,基本上是一个串建SAM的就能做的 如果要建多个串的SAM应该怎么做呢 首先看题,bzoj2780 我一开始的想法是SA以前的弄法,把串拼起来,中间加分隔符做SAM 这题确实可以这么做,这样根据SAM能识别所有子串的性质 而且每个节点都代表了唯一的一个串 每个询问串我们都能找到最终转移到哪(找不到就是没出现过) 问在多少个串出现过这就等价于在ST(s)的parent树的子树中,出现了多少种不同的权值 这显然可以维护dfs序,用经典的离线做法来搞 1 type node=r

[UOJ#131][BZOJ4199][NOI2015]品酒大会 后缀数组 + 并查集

[UOJ#131][BZOJ4199][NOI2015]品酒大会 试题描述 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加. 在大会的晚餐上,调酒师 Rainbow 调制了 n杯鸡尾酒.这 n杯鸡尾酒排成一行,其中第 i杯酒 (1≤i≤n ) 被贴上了一个标签 si ,每个标签都是 26 个小写英文字母之一.设 Str(l,r)表示第 l杯酒到第 r 杯酒的 r−l+1 个标签顺次连接构成的字

Uoj #131. 【NOI2015】品酒大会 后缀数组,并查集

#131. [NOI2015]品酒大会 统计 描述 提交 自定义测试 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加. 在大会的晚餐上,调酒师 Rainbow 调制了 nn 杯鸡尾酒.这 nn 杯鸡尾酒排成一行,其中第 ii 杯酒 (1≤i≤n1in) 被贴上了一个标签 sisi,每个标签都是 2626 个小写英文字母之一.设 Str(l,r)Strlr 表示第 ll 杯酒到第 rr 杯酒的 

UOJ #131 【NOI2015】 品酒大会

题目链接:品酒大会 学了后缀自动机之后再来写这道题就轻松多了-- 首先,题面中的两杯酒\(r\)相似就是这两个后缀的最长公共前缀大于等于\(r\).把串翻转过来之后就变成了两个前缀的最长公共后缀--然后就是\(parent\)树的事了-- 接着,我们要求出选出两杯\(r\)相似的酒的方案数.这个比较显然,就是枚举两个节点的\(lca\),然后子树的\(right\)集合大小 两两乘起来就可以了. 然后就是选出两个值使得乘积最大.树型\(dp\)还是比较显然的,在\(parent\)树上记录一下每