洛谷-P2178 学习笔记

题面

[NOI2015] 品酒大会

题目描述

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

在大会的晚餐上,调酒师 Rainbow 调制了 \(n\) 杯鸡尾酒。这 \(n\) 杯鸡尾酒排成一行,其中第 \(n\) 杯酒 (\(1 ≤ i ≤ n\)) 被贴上了一个标签 \(s_i\) ,每个标签都是 \(26\) 个小写 英文字母之一。设 \(str(l, r)\) 表示第 \(l\) 杯酒到第 \(r\) 杯酒的 \(r - l + 1\) 个标签顺次连接构成的字符串。若 \(str(p, p_0) = str(q, q_0)\),其中 \(1 ≤ p ≤ p_0 ≤ n\), \(1 ≤ q ≤ q_0 ≤ n\), \(p ≠ q\)\(p_0-p+1 = q_0 - 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\)) 的 美味度为 \(a_i\) 。现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 \(p\) 杯酒与第 \(q\) 杯酒调兑在一起,将得到一杯美味度为 \(a_p\times a_q\) 的 酒。现在请各位品酒师分别对于 \(r = 0,1,2,⋯,n-1\) ,统计出有多少种方法可以 选出 \(2\) 杯“ \(r\) 相似”的酒,并回答选择 \(2\) 杯“\(r\) 相似”的酒调兑可以得到的美味度的最大值。

输入格式

\(1\) 行包含 \(1\) 个正整数 \(n\) ,表示鸡尾酒的杯数。

\(2\) 行包含一个长度为 \(n\) 的字符串 \(S\),其中第 \(i\) 个字符表示第 \(i\) 杯酒的标签。

\(3\) 行包含 \(n\) 个整数,相邻整数之间用单个空格隔开,其中第 \(i\) 个整数表示第 \(i\) 杯酒的美味度 \(a_i\)

输出格式

包括 \(n\) 行。

\(i\) 行输出 \(2\) 个整数,中间用单个空格隔开。第 \(1\) 个整 数表示选出两杯“ \((i - 1)\) 相似”的酒的方案数,第 2 个整数表示选出两杯 “ \((i - 1)\) 相似”的酒调兑可以得到的最大美味度。若不存在两杯“ \((i - 1)\) 相似” 的酒,这两个数均为 \(0\)

样例 #1

样例输入 #1

10
ponoiiipoi
2 1 4 7 4 8 3 6 4 7

样例输出 #1

45 56
10 56
3 32
0 0
0 0
0 0
0 0
0 0
0 0
0 0

样例 #2

样例输入 #2

12
abaabaabaaba
1 -2 3 -4 5 -6 7 -8 9 -10 11 -12

样例输出 #2

66 120
34 120
15 55
12 40
9 27
7 16
5 7
3 -4
2 -4
1 -4
0 0
0 0

提示

【样例说明 1】

用二元组 \((p, q)\) 表示第 \(p\) 杯酒与第 \(q\) 杯酒。

\(0\) 相似:所有 \(45\) 对二元组都是 \(0\) 相似的,美味度最大的是 $8 × 7 = 56 $。

\(1\) 相似: $(1,8) (2,4) (2,9) (4,9) (5,6) (5,7) (5,10) (6,7) (6,10) (7,10) $,最大的 \(8 × 7 = 56\)

\(2\) 相似: \((1,8) (4,9) (5,6)\) ,最大的 \(4 × 8 = 32\)

没有 \(3,4,5, ⋯ ,9\) 相似的两杯酒,故均输出 \(0\)

【时限1s,内存512M】

简要题意

  • \(n\) 的字符串 \(s\)

  • 对于 \(r \in [0, n)\)

    • \(\forall 1\le i < j \le n \space lcp(i, j) \ge r\) 的对数。

    • 以及 满足 \(lcp(i, j) \ge r\)\(a_i \times a_j\) 的最大值。

思想

Step 1

看到 \(LCP\) 想到 \(Suffix\space Array\)

问题转化成满足 \(\min \limits_{x=sa_i + 1}^{sa_j} height_x \ge r\) 的答案。

Step 2

大于等于很难受,我们把他转化成等于再做后缀和与最大值就行了。

问题转化成满足 \(\min \limits_{x=sa_i + 1}^{sa_j} height_x = r\) 的答案。

Step 3

要求一个区间内某最小值等于某值

经典套路

一般使用笛卡尔树或并查集。

这里使用并查集。

从大到小枚举height,每次把左右两个区间合并并计算答案。

Step 4

考虑合并的时候如何更新。

第一问的答案只有在横跨了枚举的值时才会产生贡献。

将左右区间的长度相乘即可。

第二问的答案就拿左边的最大最小,右边的最大最小乘,因为可能有负数。

Talk is cheap, show me the code

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 3e5 + 5;
constexpr long long INF = 0x3f3f3f3f3f3f3f3f;

int cnt[MAXN], oldrk[MAXN * 2], id[MAXN], key1[MAXN];

bool cmp(int x, int y, int w) {
    return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}

struct SuffixArray {
    int n, sa[MAXN], rk[MAXN], height[MAXN];
    void init(string s) {
        memset(cnt, 0, sizeof cnt);
        memset(oldrk, 0, sizeof oldrk);
        memset(id, 0, sizeof id);
        memset(key1, 0, sizeof key1);
        memset(sa, 0, sizeof sa);
        memset(rk, 0, sizeof rk);
        memset(height, 0, sizeof height);
        n = int(s.size());
        int m = 27, p;
        s = " " + s;
        for (int i = 1; i <= n; i++)
            cnt[rk[i] = s[i] - 'a' + 1]++;
        for (int i = 1; i <= m; i++)
            cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; i--)
            sa[cnt[rk[i]]--] = i;

        for (int w = 1;; w <<= 1, m = p) {
            p = 0;
            for (int i = n; i > n - w; i--)
                id[++p] = i;
            for (int i = 1; i <= n; i++)
                if (sa[i] > w)
                    id[++p] = sa[i] - w;

            memset(cnt, 0, sizeof cnt);
            for (int i = 1; i <= n; i++)
                cnt[key1[i] = rk[id[i]]]++;
            for (int i = 1; i <= m; i++)
                cnt[i] += cnt[i - 1];
            for (int i = n; i >= 1; i--)
                sa[cnt[key1[i]]--] = id[i];

            memcpy(oldrk + 1, rk + 1, n * sizeof(int));
            p = 0;
            for (int i = 1; i <= n; i++) {
                if (cmp(sa[i], sa[i - 1], w))
                    rk[sa[i]] = p;
                else
                    rk[sa[i]] = ++p;
            }
            if (p == n)
                break;
        }

        for (int i = 1, k = 0; i <= n; i++) {
            if (rk[i] == 0)
                continue;
            if (k)
                k--;
            while (s[i + k] == s[sa[rk[i] - 1] + k])
                k++;
            height[rk[i]] = k;
        }
    }

    bool ask(int l, int r) {
        return height[l] > height[r];
    }
} sa;

int n;
string s;
int a[MAXN], idx[MAXN];
long long res1[MAXN], res2[MAXN];

struct Union {
    int n; // node sums
    int p[MAXN]; // parent
    long long ans[MAXN], mx[MAXN], mn[MAXN], sz[MAXN];

    void init(int _n) {
        n = _n;
        for (int i = 1; i <= n; i++)
            p[i] = i, sz[i] = 1, mx[i] = mn[i] = a[i], ans[i] = -INF;
    }

    int get(int x) {
        if (x == p[x])
            return x;
        return p[x] = get(p[x]);
    }

    void merge(int x, int y, int len) {
        x = get(x);
        y = get(y);
        p[y] = x;
        res1[len] += 1ll * sz[x] * sz[y];
        sz[x] += sz[y];
        ans[x] = max({ ans[x], ans[y], 1ll * mx[x] * mx[y], 1ll * mx[x] * mn[y], 1ll * mn[x] * mx[y], 1ll * mn[x] * mn[y] });
        mx[x] = max(mx[x], mx[y]);
        mn[x] = min(mn[x], mn[y]);
        res2[len] = max(res2[len], ans[x]);
    }
} u;

bool cmp2(int x, int y) {
    return sa.ask(x, y);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> s;
    for (int i = 1; i <= n; i++)
        cin >> a[i], idx[i] = i;

    u.init(n);

    sa.init(s);

    sort(idx + 2, idx + n + 1, cmp2);

    memset(res1, 0, sizeof res1);
    memset(res2, 0xc0, sizeof res2);

    for (int i = 2; i <= n; i++)
        u.merge(sa.sa[idx[i]], sa.sa[idx[i] - 1], sa.height[idx[i]]);

    for (int i = n - 1; i >= 0; i--)
        res1[i] += res1[i + 1];
    for (int i = n - 1; i >= 0; i--)
        res2[i] = max(res2[i], res2[i + 1]);

    for (int i = 0; i < n; i++) {
        cout << res1[i] << " ";
        if (res1[i] == 0)
            cout << 0 << endl;
        else
            cout << res2[i] << endl;
    }

        return 0;
}

运用到的知识点&Trick

SA(知识点)

\(Suffix\space Array\) 中后缀 \(i\) 与 后缀 \(j\)\(LCP\)\(\min \limits_{x=sa_i + 1}^{sa_j} height_x\)

并查集(Trick)

求满足区间最小/最大值等于某数的答案。

从大往小/从小往大枚举最小/最大值并把左右区间合并计算答案。

posted @ 2024-03-21 13:23  LightningCreeper  阅读(8)  评论(0编辑  收藏  举报