NOI 2015 品酒大会 后缀数组

在幻影阁夏日品酒大会上,通过后缀数组及并查集的方法解决了寻找相似鸡尾酒组合的问题,旨在找出所有可能的“r相似”酒款组合及其最大美味度。

  一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战 两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。

  在大会的晚餐上,调酒师 Rainbow 调制了 n 杯鸡尾酒。这 n 杯鸡尾酒排成一行,其中第 n 杯酒 (1 ≤ i ≤ n) 被贴上了一个标签si,每个标签都是 26 个小写 英文字母之一。设 str(l, r)表示第 l 杯酒到第 r 杯酒的 r − l + 1 个标签顺次连接构成的字符串。若 str(p, po) = str(q, qo),其中 1 ≤ p ≤ po ≤ n, 1 ≤ q ≤ qo ≤ n, p ≠ q, po − p + 1 = qo − q + 1 = r ,则称第 p 杯酒与第 q 杯酒是“ r 相似” 的。当然两杯“ r 相似”(r > 1)的酒同时也是“ 1 相似”、“ 2 相似”、……、“ (r − 1) 相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q ,第 p 杯酒和第 q 杯酒都 是“ 0 相似”的。

  在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i 杯酒 (1 ≤ i ≤ n) 的 美味度为 ai 。现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 p 杯酒与第 q 杯酒调兑在一起,将得到一杯美味度为 ap*aq 的 酒。现在请各位品酒师分别对于 r = 0,1,2, ⋯ , n − 1 ,统计出有多少种方法可以 选出 2 杯“ r 相似”的酒,并回答选择 2 杯“ r 相似”的酒调兑可以得到的美味度的最大值。

思路:
  很容易想到后缀数组,然后就不会了。。。
  考虑将height数组从大到小排序,然后按顺序每次将其两边的后缀集合合并在一个集合内(用并查集即可,最开始所有后缀都在不同集合内)。
  由于是按从大到小的顺序排序的,若从这两个集合内的分别任意选取两个后缀,其LCP必等于h[i], 这两个集合的合并对任意r<= h[i]的答案都有其大小乘积的贡献。
  最大值同样维护即可,注意由于可能有负数,所以可以再维护一个最小值。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

#define For(i,j,k) for(int i = j;i <= k;i++)
#define Forr(i,j,k) for(int i = j;i >= k;i--)

const int N = 300010;

using namespace std;

char S[N];
int sa[N], rank[N], h[N], T1[N], T2[N], c[N], n;

void buildsa(int m){
    int *x = T1, *y = T2;
    For(i,1,n) c[x[i] = S[i]]++;
    For(i,1,m) c[i] += c[i-1];
    Forr(i,n,1) sa[c[x[i]]--] = i;
    for(int k = 1;k < n;k <<= 1){
        int p = 0;
        For(i,n-k+1,n) y[++p] = i;
        For(i,1,n) if(sa[i] > k) y[++p] = sa[i] - k;
        For(i,1,m) c[i] = 0;
        For(i,1,n) ++c[x[y[i]]];
        For(i,1,m) c[i] += c[i-1];
        Forr(i,n,1) sa[c[x[y[i]]]--] = y[i];
        swap(x, y);
        x[sa[1]] = p = 1;
        For(i,2,n) x[sa[i]] = y[sa[i]] == y[sa[i-1]] && 
                y[sa[i] + k] == y[sa[i-1] + k] ? p : ++p;
        if(p >= n) break;
        m = p;
    }
}

bool cmp(int x, int y){
    return h[x] > h[y];
}

void buildheight(){
    For(i,1,n) rank[sa[i]] = i;
    int p = 0;
    For(i,1,n){
        if(p) --p;
        int j = sa[rank[i] + 1];
        if(!j) continue;
        while(S[i + p] == S[j + p]) ++p;
        h[rank[i]] = p;
    }
    For(i,1,n) c[i] = i;
    sort(c + 1, c + n, cmp);
}

int fa[N], sz[N], Max[N], Min[N];
long long cnt[N], Maxa[N];

int find(int x){
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

int main(){
    scanf("%d%s", &n, S + 1);
    buildsa('z'), buildheight();
    For(i,1,n){
        int t = rank[i];
        scanf("%d", &Max[t]);
        fa[t] = t, sz[t] = 1, Min[t] = Max[t], Maxa[i-1] = -5e18;
    }
    For(i,1,n-1){
        int x = find(c[i]), y = find(c[i] + 1);
        cnt[h[c[i]]] += 1LL * sz[x] * sz[y];
        Maxa[h[c[i]]] = max(Maxa[h[c[i]]], 
            max(1LL * Min[x] * Min[y], 1LL * Max[x] * Max[y]));
        sz[y] += sz[x];
        Max[y] = max(Max[y], Max[x]), Min[y] = min(Min[y], Min[x]);
        fa[x] = y;
    }
    Forr(i,n-2,0) 
        cnt[i] += cnt[i+1], Maxa[i] = max(Maxa[i], Maxa[i+1]);
    For(i,0,n-1) printf("%lld %lld\n", cnt[i], cnt[i] ? Maxa[i] : 0);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值