您的位置:首页 > 其它

NOI2015.品酒大会(后缀数组)

2016-01-08 14:06 134 查看
给出一个 长度为 n
的字符串,每一位有一个权值 val。定义两个位字符为 r 相似,是指分别从这两个字符开始,到后面的 r
个字符都相等。两个 r 相似的字符还有一个权值为这两个字符权值的乘积。问对于 r = 0, 1, 2, … , n - 1,统计出有多少种方法可以选出
2 个“r 相似”的字符,并回答选择 2 个”r
相似”的字符可以得到的权值的最大值。

首先说一个暴力的做法,可以得到
50
分:

先预处理出 1 - n 累加的结果。对于这个字符串求出
Height 数组,然后枚举 r ,按 r
Height
分组,然后统计方案数:假设组内有 x 个后缀,那么贡献的方案数就是 1 + 2 + ... + (x - 1),统计最大值:维护最大值和次大值,最小值和次小值,更新答案。

这种方法的复杂度主要看数据,如果数据很乱,导致 Height数组的最大值很小,那么这种方法就可以过,否则就会 TLE

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

using namespace std;

const int MAX_N = 300005;
const long long INF = 9223372036854775807;

typedef long long LL;

int n, a[MAX_N], sa[MAX_N], r[MAX_N], h[MAX_N], id1, id2;
LL val[MAX_N], Mx, Mxx, Mn, Mnn;
int ws[MAX_N], wv[MAX_N], wa[MAX_N], wb[MAX_N];
LL add[MAX_N], ans, ret;

void da(int *a, int *sa, int n, int m) {
int *x = wa, *y = wb;
for (int i = 0; i < m; i ++) ws[i] = 0;
for (int i = 0; i < n; i ++) ws[x[i] = a[i]] ++;
for (int i = 1; i < m; i ++) ws[i] += ws[i - 1];
for (int i = n - 1; i >= 0; i --) sa[-- ws[x[i]]] = i;
for (int k = 1; k <= n; k <<= 1) {
int p = 0;
for (int i = n - k; i < n; i ++) y[p ++] = i;
for (int i = 0; i < n; i ++) if (sa[i] >= k) y[p ++] = sa[i] - k;
for (int i = 0; i < n; i ++) wv[i] = x[y[i]];
for (int i = 0; i < m; i ++) ws[i] = 0;
for (int i = 0; i < n; i ++) ws[wv[i]] ++;
for (int i = 1; i < m; i ++) ws[i] += ws[i - 1];
for (int i = n - 1; i >= 0; i --) sa[-- ws[wv[i]]] = y[i];
swap(x, y); p = 1; x[sa[0]] = 0;
for (int i = 1; i < n; i ++) x[sa[i]] = (y[sa[i - 1]] == y[sa[i]]) && (y[sa[i - 1] + k] == y[sa[i] + k]) ? p - 1 : p ++;
if (p >= n) break; m = p;
}
}
void calc() {
for (int i = 1; i <= n; i ++) r[sa[i]] = i;
int k = 0, j;
for (int i = 0; i < n; h[r[i ++]] = k)
for (k ? k -- : 0, j = sa[r[i] - 1]; a[i + k] == a[j + k]; k ++);
}
void work(int x) {
int sum = 0, j;
for (int i = 2; i <= n; i = j + 1) {
for (; h[i] < x && i <= n; i ++);
for (j = i; h[j] >= x; j ++);
if (i == j) continue;
sum = 0; Mx = -INF; Mn = Mnn = INF;
ans += add[j - i];
for (int k = i - 1; k < j; k ++) {
if (val[sa[k]] > Mx) Mx = val[sa[k]], id1 = k;
if (val[sa[k]] < Mn) Mn = val[sa[k]], id2 = k;
}
Mxx = -INF, Mnn = INF;
for (int k = i - 1; k < j; k ++) {
if (val[sa[k]] > Mxx && id1 != k) Mxx = val[sa[k]];
if (val[sa[k]] < Mnn && id2 != k) Mnn = val[sa[k]];
}
ret = max(ret, max(Mxx * Mx, Mn * Mnn));
}
}
void init() {
scanf("%d", &n); getchar();
for (int i = 0; i < n; i ++) {
char c; scanf("%c", &c);
a[i] = (int)c;
}
a
= 0;
Mx = Mxx = -INF; Mn = Mnn = INF;
for (int i = 0; i < n; i ++) {
scanf("%lld", &val[i]);
if (Mx < val[i]) Mx = val[i], id1 = i;
if (Mn > val[i]) Mn = val[i], id2 = i;
}
da(a, sa, n + 1, 128); calc();
for (int i = 1; i <= n; i ++) printf("%d ", sa[i]); printf("\n");
for (int i = 1; i <= n; i ++) printf("%d ", h[i]); printf("\n");
}
void doit() {
int mxh = -1;
for (int i = 1; i <= n; i ++) mxh = max(mxh, h[i]);
for (int i = 1; i <= n; i ++) add[i] = add[i - 1] + i;
for (int i = 0; i < n; i ++) {
if (Mxx < val[i] && i != id1) Mxx = val[i];
if (Mnn > val[i] && i != id2) Mnn = val[i];
}
printf("%lld %lld\n", add[n - 1], max(Mx * Mxx, Mn * Mnn));
for (int i = 1; i < n; i ++) {
if (i <= mxh) {
ans = 0; ret = -INF;
Mx = Mxx = -INF; Mn = Mnn = INF;
work(i);
printf("%lld %lld\n", ans, ret == -INF ? 0 : ret);
} else printf("0 0\n");
}
}
int main() {
init();
doit();
return 0;
}
满分算法:

Height 数组,然后按从大到小的顺序排序,因为可以发现
Height 中的大值不会影响小值对答案的贡献。每次更新答案,将当前两个字符串合并,即用并查集维护一下,他们对于方案数的贡献就是这两个后缀所在的集合个数的乘积,同时维护一下最大最小值就行了。时间复杂度 O( nlogn )

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

using namespace std;

const int MAX_N = 300005;

typedef long long LL;

int n, a[MAX_N], sa[MAX_N], r[MAX_N], h[MAX_N];
int ws[MAX_N], wv[MAX_N], wa[MAX_N], wb[MAX_N];
int f[MAX_N], sz[MAX_N];
LL ans[MAX_N], sum[MAX_N], mx[MAX_N], mn[MAX_N], val[MAX_N];
char c;
struct node {
int h, x, y;
}g[MAX_N];

inline bool cmp(node a, node b) { return a.h > b.h; }
int find(int x) { return x == f[x] ? x : (f[x] = find(f[x])); }
void uniont(int x, int y) {
f[y] = x; sz[x] += sz[y];
mx[x] = max(mx[x], mx[y]);
mn[x] = min(mn[x], mn[y]);
}
void da(int *a, int *sa, int n, int m) {
int *x = wa, *y = wb;
for (int i = 0; i < m; i ++) ws[i] = 0;
for (int i = 0; i < n; i ++) ws[x[i] = a[i]] ++;
for (int i = 1; i < m; i ++) ws[i] += ws[i - 1];
for (int i = n - 1; i >= 0; i --) sa[-- ws[x[i]]] = i;
for (int k = 1; k <= n; k <<= 1) {
int p = 0;
for (int i = n - k; i < n; i ++) y[p ++] = i;
for (int i = 0; i < n; i ++) if (sa[i] >= k) y[p ++] = sa[i] - k;
for (int i = 0; i < n; i ++) wv[i] = x[y[i]];
for (int i = 0; i < m; i ++) ws[i] = 0;
for (int i = 0; i < n; i ++) ws[wv[i]] ++;
for (int i = 1; i < m; i ++) ws[i] += ws[i - 1];
for (int i = n - 1; i >= 0; i --) sa[-- ws[wv[i]]] = y[i];
swap(x, y); p = 1; x[sa[0]] = 0;
for (int i = 1; i < n; i ++) x[sa[i]] = (y[sa[i - 1]] == y[sa[i]]) && (y[sa[i - 1] + k] == y[sa[i] + k]) ? p - 1 : p ++;
if (p >= n) break; m = p;
}
}
void calc() {
for (int i = 1; i <= n; i ++) r[sa[i]] = i;
int k = 0, j;
for (int i = 0; i < n; h[r[i ++]] = k)
for (k ? k -- : 0, j = sa[r[i] - 1]; a[i + k] == a[j + k]; k ++);
}
void init() {
scanf("%d", &n); getchar();
for (int i = 0; i < n; i ++) {
char c; scanf("%c", &c);
a[i] = (int)c;
}
a
= 0;
for (int i = 1; i <= n; i ++) scanf("%lld", &val[i]);
da(a, sa, n + 1, 128); calc();
for (int i = 1; i <= n; i ++)
f[i] = i, mx[r[i - 1]] = val[i], mn[r[i - 1]] = val[i], sz[i] = 1;
}
void doit() {
for (int i = 2; i <= n; i ++) g[i - 1].h = h[i], g[i - 1].x = i, g[i - 1].y = i - 1;
sort(g + 1, g + n, cmp);
memset(sum, 128, sizeof(sum));
for (int i = g[1].h, j = 1; i >= 0; i --) {
ans[i] = ans[i + 1], sum[i] = sum[i + 1];
for (; j < n && g[j].h == i; j ++) {
int x = find(g[j].x), y = find(g[j].y);
sum[i] = max(sum[i], 1ll * mx[x] * mx[y]);
sum[i] = max(sum[i], 1ll * mn[x] * mn[y]);
ans[i] += 1ll * sz[x] * sz[y];
uniont(x,y);
}
}
for (int i = 0; i < n; i ++)
printf("%lld %lld\n", ans[i], ans[i] == 0 ? 0 : sum[i]);
}
int main() {
init();
doit();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: