【luogu P5161】WD与数列(SA)(单调栈)
WD与数列
题目链接:luogu P5161
题目大意
给你一个序列,问你有多少对区间,长度相同,没有相交部分,而且一个区间里面所有数同时加上某个数可以变成另一个区间。
思路
首先发现它要加数那个很难处理。
那考虑一个事情其实就是直接差分一下,就变成要两个一样了。
然后再考虑,发现不能相交的条件在差分之后你就不能相邻了,你要至少隔一个数。
而且因为差分,长度为
1
1
1 的全部都被你弄掉了,要单独加上。
然后考虑怎么求两两 LCP 的和。
其实可以直接 SAM 上 rush,也可以用 SA。
用 SA 的话可以这样,用一个单调栈,因为你 LCP 在 SA 上是 height 数组的最小值,那就是所有区间的最小值的和,用单调栈维护最小值就可以。
就维护当前点作为右端点,所有左端点形成的区间的答案,然后维护这这个,每次把这个加入答案就可以。
但是你这样没有处理重合,于是考虑处理重合的部分减去。
考虑一个东西,你枚举差的位置
l
e
n
len
len。
考虑用关键点,我们把编号为
l
e
n
len
len 的倍数的都标记。
然后对于一个相邻的关键单
k
,
k
+
l
e
n
k,k+len
k,k+len,我们求
x
∈
[
k
−
l
e
n
+
1
,
k
]
x\in[k-len+1,k]
x∈[k−len+1,k],然后
y
=
x
+
l
e
n
y=x+len
y=x+len。
会发现你要重合至少长度要是
l
e
n
len
len,那一定会经过
k
k
k 这个点。
所以合法的
x
x
x 是
l
c
s
(
k
,
k
+
l
e
n
)
∼
k
lcs(k,k+len)\sim k
lcs(k,k+len)∼k。
然后再求出
l
c
p
(
k
,
k
+
l
e
n
)
lcp(k,k+len)
lcp(k,k+len),发现右边能匹配的其实固定了,所以左端点右移会使得能匹配的长度少
1
1
1,所以是等差序列求和,算一下就搞定了。
(记得算上我们预先有的
l
e
n
len
len)
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 3e5 + 100;
int n, s[N], a[N], b[N], bn, log2_[N];
ll ans;
struct SA {
int sa[N], rnk[N], x[N], y[N], fir[N], minn[N][21], height[N];
int tong[N];
void Sort(int m, int *x, int *y) {
for (int i = 1; i <= m; i++) tong[i] = 0;
for (int i = 1; i <= n; i++) tong[x[i]]++;
for (int i = 1; i <= m; i++) tong[i] += tong[i - 1];
for (int i = n; i >= 1; i--)
sa[tong[fir[i]]--] = y[i];
}
void Init() {
for (int i = 1; i <= n; i++) x[i] = a[i];
for (int i = 1; i <= n; i++) y[i] = i;
for (int i = 1; i <= n; i++) fir[i] = x[y[i]];
Sort(bn, x, y);
int m = bn;
for (int j = 1; j < n; j <<= 1) {
int ynum = 0;
for (int i = n - j + 1; i <= n; i++)
y[++ynum] = i;
for (int i = 1; i <= n; i++)
if (sa[i] > j) y[++ynum] = sa[i] - j;
for (int i = 1; i <= n; i++) fir[i] = x[y[i]];
Sort(m, x, y);
swap(x, y);
int mm = 1;
x[sa[1]] = 1;
for (int i = 2; i <= n; i++)
if (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + j] == y[sa[i - 1] + j]) x[sa[i]] = mm;
else x[sa[i]] = ++mm;
m = mm;
if (m == n) break;
}
int k = 0, j;
for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
for (int i = 1; i <= n; i++) {
if (rnk[i] == 1) {height[rnk[i]] = k; continue;}
if (k) k--;
j = sa[rnk[i] - 1];
while (a[i + k] == a[j + k] && i + k <= n && j + k <= n) k++;
height[rnk[i]] = k;
}
for (int i = 1; i <= n; i++) minn[i][0] = height[i];
for (int i = 1; i <= 20; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++)
minn[j][i] = min(minn[j][i - 1], minn[j + (1 << (i - 1))][i - 1]);
}
int LCP(int x, int y) {
x = rnk[x]; y = rnk[y];
if (x > y) swap(x, y); x++;
int k = log2_[y - x + 1];
return min(minn[x][k], minn[y - (1 << k) + 1][k]);
}
int sta[N], tot;
void work() {
tot = 0; sta[++tot] = 1; ll now = 0;
for (int i = 2; i <= n; i++) {
while (tot > 1 && height[sta[tot]] > height[i]) {
now -= 1ll * height[sta[tot]] * (sta[tot] - sta[tot - 1]);
tot--;
}
sta[++tot] = i; now += 1ll * height[sta[tot]] * (sta[tot] - sta[tot - 1]);
ans += now;
}
}
}T, Tf;
ll calc(int s, int t, int l) {
int R = min(s, l) + t - 1, L = max(t, l);
if (L <= R) return 1ll * (R - L + 1) * (L - l + 1 + R - l + 1) / 2;
return 0;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &s[i]);
log2_[0] = -1; for (int i = 1; i <= n; i++) log2_[i] = log2_[i >> 1] + 1;
ans = 1ll * n * (n + 1) / 2 - n;
n--;
for (int i = 1; i <= n; i++) a[i] = s[i + 1] - s[i], b[i] = a[i];
sort(b + 1, b + n + 1); bn = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + bn + 1, a[i]) - b;
T.Init(); T.work();
reverse(a + 1, a + n + 1); Tf.Init();
reverse(a + 1, a + n + 1);
for (int k = 1; k <= n; k++)
for (int r = k + k; r <= n; r += k) {
int l = r - k; ans -= calc(Tf.LCP(n - r + 1, n - l + 1), T.LCP(l, r), k);
}
printf("%lld", ans);
return 0;
}