题面
[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)
求满足区间最小/最大值等于某数的答案。
从大往小/从小往大枚举最小/最大值并把左右区间合并计算答案。
标签:memset,洛谷,int,P2178,笔记,杯酒,MAXN,sa,sizeof From: https://www.cnblogs.com/LightningCreeper/p/18087173