LOJ 3311 「ZJOI2020」字符串

这个题刚开始胡了个假做法,导致做完整个题用了一整个下午,我果然还是太菜了……

题意

长度为 n n 的串, m m 组询问,求区间本质不同平方串个数。

n , m 200000 n,m \le 200000

题解

首先有若干个 djq 说早就被普及了的结论,也不一定需要都用上。

引理 1(runs theorem): run 的个数不超过 n 1 n-1

证明大概是每个 run 的 lyndon 根不交。

引理 2: 每个位置结尾的本原平方串个数为 O ( log n ) O(\log n)

这个证明大概是,如果存在三个串 u , v , w u,v,w u < v < w |u|<|v|<|w| ,并且 u u uu v v vv 的后缀, v v vv w w ww 的后缀,则 u + v w |u|+|v| \le |w| 。具体细节好像比较困难,见 zzq 论文。

引理 3: 本质不同的本原平方串个数不超过 2 n 2n

证明:若一个位置同时有 u u , v v , w w uu,vv,ww 三个本原平方串以其为结尾且未在前面出现,由于 u + v w |u|+|v| \le |w| 2 u < w 2|u| < |w| ,因此 u u uu w w 的后缀,已经出现过一次。因此每个位置最多新产生 2 2 个本原平方串。(听说这个引理可以推广到“本质不同的平方串个数不超过 2 n 2n ”,不过我不会证……)

接下来是做法。

因此就轻易获得了一个 O ( n n log n ) O(n \sqrt n \log n) 的莫队做法,直接每个位置算出以他为结尾的本原平方串有哪些,暴力更新即可。(听说现场这个做法跑过去了)

不过还有一个比较明显的思路就是做扫描线。一个暴力的做法大概就是,假设当前扫到了位置 i i ,找出所有以 i i 为结尾的平方串(没有本原),然后把它上次出现位置的权值 --,当前位置权值 ++,询问直接查询区间和即可。(听说数据比较水,所以这个 n 2 log n n^2 \log n 的做法剪剪枝就过了?)

不过这种做法可以稍加改进,就不用修改这么多位置了。djq 好像用的是某种数点,本蒟蒻试图理解但是失败了,只好自己 yy 了一个……

考虑为什么要修改以 i i 结尾的所有平方串,这是因为有可能有若干 runs 经过了 i i (即当前扫到的点),如果我们不暴力更新就会 WA。所以我们先不考虑经过当前询问右端点的 runs,只计算其他 runs 对答案的贡献。同时,为了避免重复计算,我们还是需要动态维护只考虑 [ 1 , i ] [1,i] 这个前缀时,每种平方串最后一次出现在哪个 runs 中。

现在就非常容易计算了,流程大致如下:

  1. 遍历以 i i 结尾的所有本原平方串,找到其对应的以 i i 结尾的极长平方串,更新该平方串最后一次的出现位置,并把上次出现位置的权值 --。
  2. 遍历右端点为 i i 的所有询问,先在 BIT 上询问出不算经过右端点的 runs,答案是多少。然后遍历以 i i 结尾的所有本原平方串,计算它对当前询问的贡献。假设有效区间长度为 l l ,周期为 p p ,那么贡献应该是 i = 0 p 1 l i 2 p \displaystyle \sum_{i=0}^{p-1}\left\lfloor\frac{l-i}{2p}\right\rfloor 。这个东西随便讨论一下就可以 O ( 1 ) O(1) 算了。
  3. 遍历所有右端点为 i i 的 runs,把它包含的所有平方串的最后一次出现位置权值 ++。注意步骤 1 中已经去过重了,所以这里不需要去重。

这样就计算完了。考虑一下复杂度,建 SA,算 runs 视为 O ( n log n ) O(n \log n) ,步骤 1 总共会枚举 O ( n log n ) O(n \log n) 次,步骤 2 会枚举 O ( n ) O(n) 次,步骤 3 也可以视为对于每个结尾枚举其本原平方串,次数也是 O ( n log n ) O(n \log n) ,加上 BIT,因此总复杂度为 O ( n log 2 n ) O(n \log^2 n)

不知道能不能更优,如果能做到单 log \log 的话请赐教 /kel

写博客时还是榜 rk1

没写调和级数算本原平方串,写的是 lyndon array 算 runs……

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template<typename T> void chkmax(T &a, const T &b) { a = a > b ? a : b; }
template<typename T> void chkmin(T &a, const T &b) { a = a < b ? a : b; }

namespace IO {
    const int MAXR = 10000000;
    char _READ_[MAXR], _PRINT_[MAXR];
    int _READ_POS_, _PRINT_POS_, _READ_LEN_;
    inline char readc() {
    #ifndef ONLINE_JUDGE
        return getchar();
    #endif
        if (!_READ_POS_) {
            if (feof(stdin)) return -1;
            _READ_LEN_ = fread(_READ_, 1, MAXR, stdin);
        }
        char c = _READ_[_READ_POS_++];
        if (_READ_POS_ == _READ_LEN_) _READ_POS_ = 0;
        return c;
    }
    template<typename T> inline int read(T &x) {
        x = 0; register int flag = 1, c;
        while (((c = readc()) < '0' || c > '9') && c != '-')
            if (c < 0) return -1;
        if (c == '-') flag = -1; else x = c - '0';
        while ((c = readc()) >= '0' && c <= '9') x = x * 10 - '0' + c;
        x *= flag; return 0;
    }
    inline int read(char *s) {
        register int len = 0, c;
        while (isspace(c = readc()) || c <= 0)
            if (c < 0) return -1;
        s[len++] = c;
        while (!isspace(c = readc()) && c) s[len++] = c;
        s[len] = 0;
        return len;
    }
    template<typename T1, typename ...T2> inline int read(T1 &a, T2&... x) {
        return read(a) | read(x...);
    }
    inline void ioflush() { fwrite(_PRINT_, 1, _PRINT_POS_, stdout), _PRINT_POS_ = 0; fflush(stdout); }
    inline void printc(char c) {
        if (!c) return;
        _PRINT_[_PRINT_POS_++] = c;
        if (_PRINT_POS_ == MAXR) ioflush();
    }
    template<typename T> inline void print(T x, char c = '\n') {
        if (x < 0) printc('-'), x = -x;
        if (x) {
            static char sta[20];
            register int tp = 0;
            for (; x; x /= 10) sta[tp++] = x % 10 + '0';
            while (tp > 0) printc(sta[--tp]);
        } else printc('0');
        printc(c);
    }
    inline void print(char *s, char c = '\n') {
        for (int i = 0; s[i]; i++) printc(s[i]);
        printc(c);
    }
    inline void print(const char *s, char c = '\n') {
        for (int i = 0; s[i]; i++) printc(s[i]);
        printc(c);
    }
    template<typename T1, typename ...T2> inline void print(T1 x, T2... y) {
        print(x, ' '), print(y...);
    }
}

namespace HM {
	static const int SIZE = 1 << 20, MOD = SIZE - 1;
	LL key[SIZE]; int val[SIZE];
	int hash(LL x) { return (x >> 43 ^ x >> 25 ^ x << 14 ^ x >> 7 ^ x) & MOD; }
	int& get(LL x) {
		int p = hash(x), s = (p & 7) | 1;
		for (; key[p] && key[p] != x; p = (p + s) & MOD);
		key[p] = x;
		return val[p];
	}
}

const int MAXN = 200005;
int n, m;
char str[MAXN];
namespace Hash {
	const int MOD1 = 1004535809, MOD2 = 1000000009, BASE1 = 14, BASE2 = 35;
	LL hsh1[MAXN], hsh2[MAXN], pw1[MAXN], pw2[MAXN];
	LL get_hash(int l, int r) {
		return --l, (hsh1[r] + (MOD1 - hsh1[l]) * pw1[r - l]) % MOD1 << 32 |
		            (hsh2[r] + (MOD2 - hsh2[l]) * pw2[r - l]) % MOD2;
	}
	
	int lcp(int a, int b) {
		if (str[a] != str[b]) return 0;
		int l = 0, r = min(n - b, n - a) + 1;
		while (l + 1 < r) {
			int mid = (l + r) >> 1;
			if (get_hash(a, a + mid) == get_hash(b, b + mid)) l = mid;
			else r = mid;
		}
		return r;
	}
	
	int lcs(int a, int b) {
		if (str[a] != str[b]) return 0;
		int l = 0, r = min(a, b);
		while (l + 1 < r) {
			int mid = (l + r) >> 1;
			if (get_hash(a - mid, a) == get_hash(b - mid, b)) l = mid;
			else r = mid;
		}
		return r;
	}
	
	void init() {
		pw1[0] = pw2[0] = 1;
		for (int i = 1; i <= n; i++) {
			pw1[i] = pw1[i - 1] * BASE1 % MOD1;
			pw2[i] = pw2[i - 1] * BASE2 % MOD2;
			hsh1[i] = (hsh1[i - 1] * BASE1 + str[i]) % MOD1;
			hsh2[i] = (hsh2[i - 1] * BASE2 + str[i]) % MOD2;
		}
	}
}

struct Runs {
	int l, r, p;
	bool operator<(const Runs &q) const {
		return r == q.r ? p < q.p : r < q.r;
	}
	bool operator==(const Runs &q) const {
		return r == q.r && p == q.p && l == q.l;
	}
} runs[MAXN << 1];
int ans[MAXN], bit[MAXN], tot;
struct Qry { int l, id; };
vector<Qry> qry[MAXN];
vector<Runs> vr[MAXN];
vector<int> md[MAXN];

void add(int x, int y) {
	for (; x <= n; x += x & -x) bit[x] += y;
}
int ask(int x) {
	int s = 0;
	for (; x; x -= x & -x) s += bit[x];
	return s;
}

namespace SA {
	int sa[MAXN], fst[MAXN], sec[MAXN], cnt[MAXN], lyn[2][MAXN];
	int sta[2][MAXN];
	int cmp(int a, int b, int l) {
		return a + l * 2 > n + 1 || b + l * 2 > n + 1 ||
			sec[a] != sec[b] || sec[a + l] != sec[b + l];
	}
	
	void init(char *s) {
		for (int i = 1; i <= n; i++) ++cnt[fst[i] = s[i]];
		int m = 128;
		for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
		for (int i = n; i > 0; i--) sa[cnt[fst[i]]--] = i;
		for (int i = 1; i < n; i <<= 1) {
			int p = 0;
			for (int j = n - i + 1; j <= n; j++) sec[++p] = j;
			for (int j = 1; j <= n; j++) if (sa[j] > i) sec[++p] = sa[j] - i;
			memset(cnt, 0, sizeof(cnt));
			for (int j = 1; j <= n; j++) ++cnt[fst[j]];
			for (int j = 1; j <= m; j++) cnt[j] += cnt[j - 1];
			for (int j = n; j > 0; j--) sa[cnt[fst[sec[j]]]--] = sec[j];
			memcpy(sec, fst, sizeof(sec));
			m = fst[sa[1]] = 1;
			for (int j = 2; j <= n; j++)
				fst[sa[j]] = (m += cmp(sa[j - 1], sa[j], i));
			if (m > n) break;
		}
		sa[0] = n + 1;
		for (int i = 0; i <= n; i++) fst[sa[i]] = i;
//		for (int i = 1; i <= n; i++) printf("%d\n", sa[i]);
		
		// init lyndon array
		int tp[2] = { 0, 0 };
		sta[0][++tp[0]] = 0, sta[1][++tp[1]] = n + 1;
		sa[n + 1] = n + 1;
		for (int i = n; i > 0; i--) {
			while (tp[0] > 0 && fst[i] < sta[0][tp[0]]) --tp[0];
			while (tp[1] > 0 && fst[i] > sta[1][tp[1]]) --tp[1];
			if (fst[i + 1] > fst[i]) {
				lyn[1][i] = 1;
				lyn[0][i] = sa[sta[0][tp[0]]] - i;
			} else {
				lyn[0][i] = 1;
				lyn[1][i] = sa[sta[1][tp[1]]] - i;
			}
			sta[0][++tp[0]] = sta[1][++tp[1]] = fst[i];
		}
		
		// calculate runs
		int lst = 1;
		for (int i = 1; i < n; i++) {
			int x = max(lyn[0][i], lyn[1][i]);
			assert(x > 1);
			int l = Hash::lcs(i, i + x), r = Hash::lcp(i, i + x) + x;
			if (l + r - 1 >= 2 * x)
				runs[++tot] = Runs { i - l + 1, i + r - 1, x };
			if (str[i] == str[i + 1]) ++lst;
			else if (lst > 1) runs[++tot] = Runs { i - lst + 1, i, 1 }, lst = 1;
		}
		if (lst > 1) runs[++tot] = Runs { n - lst + 1, n, 1 };
		sort(runs + 1, runs + 1 + tot);
		tot = unique(runs + 1, runs + 1 + tot) - runs - 1;
		for (int i = 1; i <= tot; i++) {
			const Runs &r = runs[i];
			vr[r.r].emplace_back(r);
			for (int j = r.l + r.p * 2 - 1; j <= r.r; j++)
				md[j].emplace_back(i);
		}
	}
}

void solve() {
	for (int i = 1; i <= n; i++) {
		for (int j : md[i]) {
			const Runs &r = runs[j];
			int np = r.p << 1, t = (i - r.l + 1) / np, l = i - t * np + 1;
			LL h = Hash::get_hash(l, i);
			int &p = HM::get(h);
			if (p != 0) add(p, -1);
			p = 0;
		}
		int d = ask(i);
		for (const Qry &q : qry[i]) {
			ans[q.id] = d - ask(q.l - 1);
			for (int j : md[i]) {
				int l = i - max(runs[j].l, q.l) + 1, p = runs[j].p;
				int t = l % (p << 1), s = l / (p << 1);
				if (s == 0) continue;
				if (t >= p - 1) ans[q.id] += s * p;
				else ans[q.id] += s * (t + 1) + (s - 1) * (p - t - 1);
			}
		}
		for (const Runs &r : vr[i]) {
	//		printf("%d %d %d\n", r.l, r.r, r.p);
			int np = r.p << 1;
			for (int j = r.l; j + np - 1 <= r.r; j++) {
				int t = (r.r - j + 1) / np, k = j + t * np - 1;
				if (r.r - k >= r.p) continue;
				LL h = Hash::get_hash(j, k);
				int &p = HM::get(h);
				add(j, 1), p = j;
			}
		}
	}
}

int main() {
	IO::read(n, m);
	IO::read(str + 1);
	Hash::init();
	SA::init(str);
	for (int i = 1; i <= m; i++) {
		int l, r; IO::read(l, r);
		qry[r].emplace_back(Qry { l, i });
	}
	solve();
	for (int i = 1; i <= m; i++) IO::print(ans[i]);
	IO::ioflush();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WAautomaton/article/details/107186568
今日推荐