[LOJ2083] 「NOI2016」优秀的拆分(后缀数组)

题意

  • 给你一个字符串,问其所有子串的优秀拆分方案数之和,优秀拆分定义为S = AA + BB,其中A, B都是字符串。

我们发现只需要算每个位置作为AA串开头的方案数与每个串作为BB串结尾的方案数,然后相邻位置乘起来就可以了。

我们可以枚举AA串的长度 x x x,把整个序列下标为 x x x的倍数的位置标记为关键点,那么任何一个长度为 x x x的AA串它一定会跨过且仅跨过2个关键点,那么我们统计相邻两个关键点的后缀的LCP与前缀的LCS,那么我们把这个东西拼起来如果不小于 x x x,那么会贡献|LCS| + |LCP| - x + 1个开头和结尾,我们作一个区间加法,差分实现就好了,复杂度 O ( n log n ) O(\text{n log n}) O(n log n)

#include <bits/stdc++.h>

#define x first
#define y second
#define pb push_back
#define mp make_pair
#define inf (0x3f3f3f3f)
#define mem(a, b) memset(a, b, sizeof(a))
#define Rep(i, a) for (int i = 0; i < a; ++ i)
#define For(i, a, b) for (int i = a; i <= b; ++ i)
#define Forr(i, a, b) for (int i = a; i >= b; -- i)
#define Travel(i, x) for (int i = head[x]; i; i = nxt[i])

using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

template<class T>inline bool chkmax(T &_, T __) { return _ < __ ? _ = __, 1 : 0; }
template<class T>inline bool chkmin(T &_, T __) { return _ > __ ? _ = __, 1 : 0; }

inline void proStatus() {
	ifstream t("/proc/self/status");
	cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>());
}

const int N = 6e4 + 3;

char s[N];

int t, n, st[N], ed[N], lg[N];

struct Suffix_Array {

	int SA[N], rk[N], height[N];
	int tp[N], tong[N], st[17][N], m;

	void Radix_Sort() {
		For(i, 1, m) tong[i] = 0;
		For(i, 1, n) ++ tong[rk[tp[i]]];
		For(i, 1, m) tong[i] += tong[i - 1];
		Forr(i, n, 1) SA[tong[rk[tp[i]]] --] = tp[i];
	}

	int query(int l, int r) {
		l = rk[l], r = rk[r];
		if (l > r) swap(l, r);
		++ l;
		int len = lg[r - l + 1];
		return min(st[len][l], st[len][r - (1 << len) + 1]);
	}

	void build() {
		mem(tp, 0), mem(rk, 0);
		For(i, 1, n) chkmax(m, rk[i] = s[tp[i] = i]);
		Radix_Sort();
		for (int len = 1; len < n; len <<= 1) {
			int cnt = 0;
			For(i, n - len + 1, n) tp[++ cnt] = i;
			For(i, 1, n) if (SA[i] > len) tp[++ cnt] = SA[i] - len;
			Radix_Sort(), swap(rk, tp), rk[SA[1]] = m = 1;
			For(i, 2, n) {
				if (tp[SA[i]] ^ tp[SA[i - 1]] || tp[SA[i] + len] ^ tp[SA[i - 1] + len]) ++ m;
				rk[SA[i]] = m;
			}
			if (m == n) break;
		}
		For(i, 1, n) {
			int now = max(0, height[rk[i - 1]] - 1);
			while (s[i + now] == s[SA[rk[i] - 1] + now]) ++ now;
			st[0][rk[i]] = height[rk[i]] = now;
		}
		For(j, 1, 16) For(i, 1, n + 1 - (1 << j)) 
			st[j][i] = min(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
	}

} SA1, SA2; 

void update(int *a, int l, int r) { ++ a[l], -- a[r + 1]; }

int main() {

	freopen("2083.in", "r", stdin);
	freopen("2083.out", "w", stdout);

	For(i, 2, N - 1) lg[i] = lg[i >> 1] + 1;

	for (scanf("%d", &t); t -- ; mem(st, 0), mem(ed, 0)) {

		scanf("%s", s + 1), n = strlen(s + 1);
		SA1.build(), reverse(s + 1, s + n + 1), SA2.build();

		For(i, 1, n >> 1) for (int j = i * 2; j <= n; j += i) {
			int L = j - i, R = j, suf = SA1.query(L, R), pre = SA2.query(n - L + 1, n - R + 1);
			chkmin(suf, i), chkmin(pre, i);
			if (pre + suf >= i) {
				int all = pre + suf - i + 1; 
				update(st, L - pre + 1, L - pre + all - 1);
				update(ed, R + suf - all + 1, R + suf - 1);
			}
		}
		For(i, 1, n) st[i] += st[i - 1], ed[i] += ed[i - 1];

		ll ans = 0;
		For(i, 2, n) ans += 1ll * ed[i - 1] * st[i];
		cout << ans << endl;
	}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值