P2178 [NOI2015] 品酒大会 题解

P2178 [NOI2015] 品酒大会 题解

纪念一下第一道完全自己想出来的紫NOI题。

思路

由于 r 相似有单调性的性质,题目中也提示了这一点,考虑按 \(height_i\) 从大到小把所有相邻的 \(sa\) 数组内两个后缀合并,用并查集维护,发现第一问的答案就是

\[\sum_{i是并查集的根}\binom{size_i}{2} \]

因为同一个并查集内的所有后缀两两 LCP 一定不小于当前枚举的 \(r\),所以随便选两个后缀就是 r 相似的。

第二问的答案就相当于在同一个并查集里面选择两个后缀权值成绩最大,直接维护一个全局变量即可,考虑如何维护。

在合并并查集 \(x, y\) 的过程中,这两个后缀可能来自两个方向:

  1. 在所有并查集内部,已经考虑过了
  2. 一个在 \(x\) 内,一个在 \(y\)

对于第二种情况,只需要维护并查集内最大权值即可,由于权值可能是负数,所以还需要维护最小权值。

这样合并合并就做完了。

时间复杂度:\(O(n\alpha (n)\sim n\log n)\),取决于用什么求后缀数组,这里用 DC3 倍增求。

要开 long long 😭。

// Problem: P2178 [NOI2015] 品酒大会
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-02-03 09:41:57

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int N = 3e5 + 10;

string s;
int p[N], n, sa[N], sa2[N * 2], ht[N], rk[N * 2], x[N], buc[N], sig;
vector<int> t[N];
int fa[N], sz[N], mx[N], mn[N];
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
long long sum, maxc = -2e18;
void merge(int a, int b) {
    int x = find(a), y = find(b);
    if(x == y) return ;
    if(sz[x] > sz[y]) swap(x, y);
    sum -= 1ll * sz[x] * (sz[x] - 1) / 2, sum -= 1ll * sz[y] * (sz[y] - 1) / 2, maxc = max({maxc, 1ll * mx[x] * mx[y], 1ll * mn[x] * mn[y]});
    fa[x] = y, sz[y] += sz[x], mx[y] = max(mx[y], mx[x]), mn[y] = min(mn[x], mn[y]);
    sum += 1ll * (sz[y] - 1) * sz[y] / 2;
}
void SA() {
    s = " " + s;
    for(int i = 1; i <= n; i ++) rk[i] = s[i], sig = max(sig, rk[i]), buc[rk[i]] ++;
    for(int i = 1; i <= sig; i ++) buc[i] += buc[i - 1];
    for(int i = n; i; i --) sa[buc[rk[i]] --] = i;
    for(int j = 1, idx = 0; idx < n; sig = idx, j <<= 1) {
        idx = 0; for(int i = n - j + 1; i <= n; i ++) sa2[++ idx] = i;
        for(int i = 1; i <= n; i ++) 
            if(sa[i] > j) sa2[++ idx] = sa[i] - j;
        fill(buc + 1, buc + sig + 1, 0);
        for(int i = 1; i <= n; i ++) x[i] = rk[sa2[i]], buc[x[i]] ++;
        for(int i = 1; i <= sig; i ++) buc[i] += buc[i - 1];
        for(int i = n; i; i --) sa[buc[x[i]] --] = sa2[i];
        idx = 1, swap(rk, sa2), rk[sa[1]] = 1;
        for(int i = 2; i <= n; i ++)
            if(sa2[sa[i]] == sa2[sa[i - 1]] && sa2[sa[i] + j] == sa2[sa[i - 1] + j]) rk[sa[i]] = idx;
            else rk[sa[i]] = ++ idx;
    }
    for(int i = 1, l = 0; i <= n; i ++) {
        if(l) l --;
        while(s[i + l] == s[sa[rk[i] - 1] + l]) l ++;
        ht[rk[i]] = l;
        t[l].push_back(rk[i]);
    }
}
long long ans[N][2];
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> s;
    for(int i = 1; i <= n; i ++) cin >> p[i];
    SA();
    for(int i = 1; i <= n; i ++) fa[i] = i, sz[i] = 1, mx[i] = mn[i] = p[i];
    for(int i = n - 1; i >= 0; i --) {
        for(auto j : t[i])
            if(sa[j] && sa[j - 1])
                merge(sa[j], sa[j - 1]);
        ans[i][0] = sum, ans[i][1] = maxc;
    }
    for(int i = 0; i < n; i ++) {
        if(ans[i][0] == 0) cout << "0 0\n";
        else cout << ans[i][0] << ' ' << ans[i][1] << '\n';
    }

    return 0;
}
posted @ 2024-02-03 11:46  MoyouSayuki  阅读(10)  评论(0编辑  收藏  举报
:name :name