【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[klen+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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值