@loj


@description@

对于整数序列 ((a_1, a_2, ..., a_n)) 和 1 ~ n 的排列 ((p_1, p_2, ..., p_n)),称 ((a_1, a_2, ..., a_n)) 符合 ((p_1, p_2, ..., p_n)),当且仅当:

(1){a} 中任意两个数字互不相同。
(2)将 ((a_1, a_2, ..., a_n)) 从小到大排序后,将会得到 ((a_{p_1}, a_{p_2}, ..., a_{p_n}))

现在给出 1 ~ n 的排列 {p} 与序列 (h_1, h_2, ..., h_m),请你求出哪些 h 的子串符合排列 {p}。

输入格式
第一行两个空格隔开的正整数 n, m。
第二行 n 个空格隔开的正整数,表示排列 p。
第三行 m 个空格隔开的正整数,表示序列 h。

输出格式
第一行一个整数 k,表示符合 {p} 的子串个数。
第二行 k 个空格隔开的正整数,表示这些子串的起始位置(编号从 1 开始)。请将这些位置按照从小到大的顺序输出。特别地,若 k = 0,那么你也应当输出一个空行。

样例输入
5 10
2 1 5 3 4
5 6 3 8 12 7 1 10 11 9
样例输出
2
2 6

数据范围与提示
2 <= n <= m <= 1000000; 1 <= hi <= 10^9; 1 <= pi <= n。
且保证 {h} 中元素互不相同,{p} 是一个排列。

@solution@

先对问题作一步转化:求 {q} 使得 (q_{p_i} = i),即 {p} 的逆置换。
那么某个子串符合 {p} 可以等价于这个子串离散化到 1 ~ n 中后等于 q。

如果不考虑离散化,那么就是一个经典子串匹配问题,直接上 kmp。
假如子串同构的判定方法如上,即离散化后同构,是否还可以扩展一下 kmp 呢?

考虑 kmp 什么时候需要判同构:已知串 s 与串 t 同构时,在 s 末尾加一个 a,在 t 末尾加一个 b,判断 s + a 与 t + b 是否同构。
因为 s 与 t 已经同构了,只需要 a 与 b 加入进去过后仍然同构即可。
可以等价于判定 a 在 s 中的排名(s 中比 a 小的数) = b 在 t 中的排名(t 中比 b 小的数)。

查排名可以平衡树,不过这道题直接离散化 + 树状数组即可。
注意 kmp 在跳 fail 的时候,需要一个个元素的移动,因为要维护树状数组。
不过复杂度的证明是不会变的。kmp 还是 O(n),套个树状数组就是 O(nlogn) 的。

@accepted code@

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 1000000;
int n, m;
int t[2][MAXN + 5];
int lowbit(int x) {return x & -x;}
void update(int x, int k, int type) {
	for(int i=x;i<=m;i+=lowbit(i))
		t[type][i] += k;
}
int sum(int x, int type) {
	int ret = 0;
	for(int i=x;i;i-=lowbit(i))
		ret += t[type][i];
	return ret;
}
int d[MAXN + 5], p[MAXN + 5], h[MAXN + 5];
void discrete() {
	for(int i=1;i<=m;i++) d[i] = h[i];
	sort(d + 1, d + m + 1);
	for(int i=1;i<=m;i++)
		h[i] = lower_bound(d + 1, d + m + 1, h[i]) - d;
}
int f[MAXN + 5];
void get_f() {
	f[1] = 0;
	int ri = 0, le = 2;
	for(int i=2;i<=n;i++) {
		int j = f[i-1];
		while( sum(p[j+1], 0) != sum(p[i], 1) ) {
			while( ri != f[j] )
				update(p[ri--], -1, 0), update(p[le++], -1, 1);
			j = f[j];
		}
		f[i] = j + 1;
		update(p[++ri], 1, 0), update(p[i], 1, 1);
	}
}
vector<int>ans;
void get_ans() {
	for(int i=1;i<=m;i++)
		t[0][i] = t[1][i] = 0;
	int le = 1, ri = 0, j = 0;
	for(int i=1;i<=m;i++) {
		while( sum(p[j+1], 0) != sum(h[i], 1) ) {
			while( ri != f[j] )
				update(p[ri--], -1, 0), update(h[le++], -1, 1);
			j = f[j];
		}
		j++;
		update(p[++ri], 1, 0), update(h[i], 1, 1);
		if( j == n ) {
			ans.push_back(i-n+1);
			while( ri != f[j] )
				update(p[ri--], -1, 0), update(h[le++], -1, 1);
			j = f[j];
		}
	}
}
int main() {
	scanf("%d%d", &n, &m);
	for(int i=1;i<=n;i++) {
		int x; scanf("%d", &x);
		p[x] = i;
	}
	for(int i=1;i<=m;i++) scanf("%d", &h[i]);
	discrete(), get_f(), get_ans();
	printf("%d
", (int)ans.size());
	for(int i=0;i<(int)ans.size();i++)
		printf("%d%c", ans[i], (i + 1 == ans.size() ? '
' : ' '));
	if( ans.empty() ) puts("");
}

@details@

曾经想过字符串 hash,因为这个 hash 直接就是康托展开算。
发现这个题 hash 并不能前缀和相减,动态维护感觉还要写平衡树,于是放弃了。
毕竟相对于平衡树大家还是喜欢代码简短的树状数组吧。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11845570.html