1. 程式人生 > >【BZOJ5253】【2018多省省隊聯測】制胡竄

【BZOJ5253】【2018多省省隊聯測】制胡竄

【題目連結】

【思路要點】

  • 首先,一個詢問的答案只和詢問串的在主串中所有出現的位置有關。
  • 直接求解所有出現位置顯然會超時,一種可行的方法是用離線詢問+字尾自動機(樹)+線段樹合併來維護出現位置的右端點集合。
  • 先解決一個小問題:定位一個詢問可以在後綴樹上倍增在\(O(LogN)\)的時間內完成。
  • 現在我們有了一棵維護著所有詢問串出現位置的右端點的線段樹,考慮如何得到答案。
  • 考慮計算出所有的兩個斷點把所有出現位置都切斷的方案數,再用\(\binom{N-1}{2}\)減去它。
  • 考慮較靠前的斷點切斷了哪些字串。
  • 我們先排除一些特殊情況:
  • 1、較靠前的斷點不切斷任意的字串,那麼它一定在詢問串第一次出現之前,並且第二個斷點要恰好切斷所有字串,也即第二個斷點的取值範圍是詢問串所有出現位置的並。
  • 2、較靠前的斷點切斷了所有的字串,那麼較靠後的端點只需要滿足在較靠前的斷點之後即可,因此在這種情況下可能的方案數是一個等差數列的各項之和。
  • 除了上述情況外,我們需要求出的即是:\(\sum_{i=ql}^{qr}詢問串第一次出現和第i次出現的交*詢問串最後一次出現和第i+1次出現的交\),其中\([ql,qr]\)為\(i\)可能的取值範圍,可以線上段樹上二分得到。
  • 記詢問串第\(i\)次出現的左端點和右端點分別為\(L_i\)和\(R_i\)
  • 對於詢問串第一次出現和第\(i\)次出現的交,它在大部分的情況下為\(L_{i+1}-L_i\),也即\(R_{i+1}-R_i\),只有在\(i=qr\)時,它有可能為\(R_1-L_i+1\),並且,\(i=qr\)時有可能對應上述的第2種特殊情況。
  • 對於詢問串最後一次出現和第\(i+1\)次出現的交,它在大部分情況下為\(R_{i+1}-L_{last}+1\),只有在\(i=ql\)時,有可能對應上述的第1種特殊情況。
  • 那麼,我們可以對\(i=ql\),和\(i=qr\)時特殊處理。
  • 對於剩下的情況,也即\(i\in (ql,qr)\)時,我們需要求出\(\sum_{ql+1}^{qr-1}(R_{i+1}-R_i)*(R_{i+1}-L_{last}+1)\)。
  • 即\(\sum_{ql+1}^{qr-1}(R_{i+1}-R_i)*R_{i+1}-(L_{last}-1)(R_{qr}-R_{ql+1})\)。
  • 對於求和部分,是關於兩個相鄰位置的資訊,我們可以線上段樹上額外維護這個資訊。
  • 然後這道題就做完了。時間複雜度\(O(QLogN+NLogN)\)。

【程式碼】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXP = 200005;
const int MAXQ = 300005;
const int MAXS = 3e6 + 5;
const int MAXLOG = 20;
const int MAXC = 10;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct query {int l, r, home; };
struct info {int Min, Max; long long sum; };
info operator + (info a, info b) {
	info ans = (info) {a.Min, b.Max, a.sum + b.sum};
	if (ans.Min == 0) ans.Min = b.Min;
	if (ans.Max == 0) ans.Max = a.Max;
	if (a.Max && b.Min) ans.sum += 1ll * b.Min * (b.Min - a.Max);
	return ans;
}
struct SegmentTree {
	struct Node {
		int lc, rc;
		info val;
	} a[MAXS];
	int size, n;
	void init(int x) {
		n = x;
		size = 0;
	}
	void update(int root) {
		a[root].val = a[a[root].lc].val + a[a[root].rc].val;
	}
	void insert(int &root, int l, int r, int pos) {
		if (root == 0) root = ++size;
		if (l == r) {
			a[root].val = (info) {l, l, 0};
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) insert(a[root].lc, l, mid, pos);
		else insert(a[root].rc, mid + 1, r, pos);
		update(root);
	}
	void insert(int &root, int val) {
		insert(root, 1, n, val);
	}
	int merge(int x, int y) {
		if (x == 0 || y == 0) return x + y;
		a[x].lc = merge(a[x].lc, a[y].lc);
		a[x].rc = merge(a[x].rc, a[y].rc);
		update(x);
		return x;
	}
	void join(int &x, int y) {
		x = merge(x, y);
	}
	int lower(int root, int l, int r, int val) {
		if (a[root].val.Min >= val) return a[root].val.Min;
		int mid = (l + r) / 2;
		if (a[a[root].lc].val.Max != 0 && a[a[root].lc].val.Max >= val) return lower(a[root].lc, l, mid, val);
		else return lower(a[root].rc, mid + 1, r, val);
	}
	int upper(int root, int l, int r, int val) {
		if (a[root].val.Max <= val) return a[root].val.Max;
		int mid = (l + r) / 2;
		if (a[a[root].rc].val.Min != 0 && a[a[root].rc].val.Min <= val) return upper(a[root].rc, mid + 1, r, val);
		else return upper(a[root].lc, l, mid, val);
	}
	info query(int root, int l, int r, int ql, int qr) {
		if (ql > qr) return (info) {0, 0, 0};
		if (l == ql && r == qr) return a[root].val;
		int mid = (l + r) / 2;
		if (mid >= qr) return query(a[root].lc, l, mid, ql, qr);
		else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr);
		else return query(a[root].lc, l, mid, ql, mid) + query(a[root].rc, mid + 1, r, mid + 1, qr);
	}
	long long sum(int l, int r) {
		if (l > r) swap(l, r);
		return (l + r) * (r - l + 1ll) / 2;
	}
	long long query(int root, int l, int r) {
		if (l == r) return 0;
		int len = r - l;
		int r1 = a[root].val.Min, l1 = r1 - len + 1;
		int rn = a[root].val.Max, ln = rn - len + 1;
		int ql = lower(root, 1, n, ln);
		if (ql != r1) ql = upper(root, 1, n, ql - 1);
		else ql = 0;
		int qr = upper(root, 1, n, r1 + len - 1);
		long long ans = 0;
		if (ql > qr) return 0;
		if (ql == qr) {
			if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
			else if (qr == rn) ans += sum(n - r1, n - ln);
			else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
		} else {
			info tmp = query(root, 1, n, ql + 1, qr);
			ans += tmp.sum - (ln - 1ll) * (tmp.Max - tmp.Min);
			if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
			else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
			if (qr == rn) ans += sum(n - r1, n - ln);
			else ans += (min(r1, lower(root, 1, n, qr + 1) - len) - (qr - len + 1) + 1ll) * (lower(root, 1, n, qr + 1) - ln + 1ll);
		}
		return ans;
	}
} ST;
struct SuffixAutomaton {
	int root, size, last, len;
	int child[MAXP][MAXC], home[MAXN];
	int father[MAXP][MAXLOG], sroot[MAXP];
	int fail[MAXP], depth[MAXP], cnt[MAXP];
	vector <int> a[MAXP];
	vector <query> q[MAXP];
	long long ans[MAXQ];
	int newnode(int dep) {
		fail[size] = 0;
		depth[size] = dep;
		memset(child[size], 0, sizeof(child[size]));
		return size++;
	}
	void extend(int ch, int from) {
		int p = last, np = newnode(depth[last] + 1);
		while (child[p][ch] == 0) {
			child[p][ch] = np;
			p = fail[p];
		}
		if (child[p][ch] == np) fail[np] = root;
		else {
			int q = child[p][ch];
			if (depth[q] == depth[p] + 1) fail[np] = q;
			else {
				int nq = newnode(depth[p] + 1);
				fail[nq] = fail[q];
				fail[q] = fail[np] = nq;
				memcpy(child[nq], child[q], sizeof(child[q]));
				while (child[p][ch] == q) {
					child[p][ch] = nq;
					p = fail[p];
				}
			}
		}
		cnt[last = np]++;
		ST.insert(sroot[np], from);
		home[from] = np;
	}
	void init(char *s) {
		size = 0;
		root = last = newnode(0);
		len = strlen(s + 1);
		for (int i = 1; i <= len; i++)
			extend(s[i] - '0', i);
		for (int i = 1; i < size; i++) {
			father[i][0] = fail[i];
			a[fail[i]].push_back(i);
		}
		for (int p = 1; p < MAXLOG; p++)
		for (int i = 0; i < size; i++)
			father[i][p] = father[father[i][p - 1]][p - 1];
	}
	void storequery(int l, int r, int from) {
		int len = r - l + 1, pos = home[r];
		for (int i = MAXLOG - 1; i >= 0; i--)
			if (depth[father[pos][i]] >= len) pos = father[pos][i];
		q[pos].push_back((query) {l, r, from});
	}
	void work(int pos) {
		for (unsigned i = 0; i < a[pos].size(); i++) {
			work(a[pos][i]);
			ST.join(sroot[pos], sroot[a[pos][i]]);
		}
		for (unsigned i = 0; i < q[pos].size(); i++)
			ans[q[pos][i].home] = (len - 1ll) * (len - 2ll) / 2 - ST.query(sroot[pos], q[pos][i].l, q[pos][i].r);
	}
	void solve(int m) {
		work(0);
		for (int i = 1; i <= m; i++)
			printf("%lld\n", ans[i]);
	}
} SAM;
int n, m;
char s[MAXN];
int main() {
	read(n), read(m);
	ST.init(n);
	scanf("%s", s + 1);
	SAM.init(s);
	for (int i = 1; i <= m; i++) {
		int l, r; read(l), read(r);
		SAM.storequery(l, r, i);
	}
	SAM.solve(m);
	return 0;
}