Java教程

排列 题解

本文主要是介绍排列 题解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

题面

给定一个长度为4的排列a与一个长度为n的排列b。在b中选出长度为4的子序列使该子序列与排列a的相对顺序相同。输出选法个数。共24个subtask,意即所有排列都会出现。$ n \le 2000。 $

解法

我们考虑将这个排列a划分成两个互不相关的部分。两个部分互不相关,当且仅当他们在值域上不连续,且在位置上不连续。根据这个思路,我们可以定义“关键对”的概念。

若一个对为\((i, j)\),且 \(i < j\),其为关键对的充要条件是:\((i,j)\)、\((j,i)\)、\((a_i, a_j)\)、\((a_j, a_i)\) 不为 \((1,2)\),\((1,4)\),\((3,4)\) 中的任意一个。

这个条件是良定义的。当我们选取出一个关键对 \((i,j)\) 后,我们就可以通过在b排列上确定 \(i\) 与 \(j\) 所对应的位置来使余下的位置不连续。我们以序列 $ a = $ \(\{4,1,3,2\}\) 为例子来说明具体选取的正确性。

对于该序列,其的一个关键对是 \((2,3)\)。我们设选取子序列的第二个值位于 \(i\),第三个值位于 \(j\),则第一个值的位置位于 \([1, i-1]\) 之间,第四个值位于 \([j+1, n]\) 之间。并且,第一个值的值域位于 \([b_j+1, n]\) 之间,第四个值位于 \([1, b_i-1]\) 之间。而当我们选择 \((1,2)\) 等非关键对时,我们无法将值域与范围全部分离。因此关键对的正确性是显然的。

然而我们发现,序列\(\{2,4,1,3\}\)与\(\{3,1,4,2\}\)是无法找到关键对的。我们需要另一种特殊的解决方案。

我们可以发现,这两个序列是等价的,我们只需要在 \(a=\) \(\{3,1,4,2\}\) 的时候将a与b进行翻转,问题就可以转化为\(\{2,4,1,3\}\) 的求解。

这个序列是特殊的。我们可以看到,当第四个值确定时,第一与第三个值的值域就已经确定了。同样地,当第二个值确定时,第一与第三个值的范围也确定了。这启示我们进行第二与第四个值的枚举。

我们首先进行第三个值的枚举。设其位置为 \(i\),并定义 \(\operatorname{bound} = b_i\)。随后我们进行第四个值的枚举,设其位置为 \(j\),并设 \(k \in [1, j-1]\) 时 $ b_k$ 的全体为 \(\operatorname{front}\),\(k \in [j+1, i-1]\) 时 $ b_k$ 的全体为 \(\operatorname{back}\)。

考虑朴素的贡献度统计。我们首先将 \(\operatorname{back}\) 插入一个树状数组 \(\operatorname{BIT_b}\),并对于每个 \(\operatorname{front}\) 的值,在 \(\operatorname{BIT_b}\) 中查询。其结果设为 \(\operatorname{cont}\)。具体而言,\(\operatorname{cont}\) 代表不关心 \(b_j\) 的取值时目前的答案。当 \(b_j > \operatorname{bound}\) 时,令答案 \(+\operatorname{cont}\)。

以此方法,单次统计是 \(O(n \log n)\) 的复杂度,不可接受。然而,朴素的思路启发我们进行贡献度的优化转移。具体方法如下:

对于每次 \(j\) 向后移动一位,我们发现,实际的 \(\operatorname{cont}\) 只会受两个位置的影响:\(b_{j-1}\) 加入 \(\operatorname{front}\),以及 \(b_{j}\) 从 \(\operatorname{back}\) 中离开。我们发现,只需要保证 \(O(\log n)\) 地维护 \(\operatorname{cont}\),我们就可以在正确的复杂度下解决此题。

由于 \(b_{j}\) 离开了 \(\operatorname{back}\),因此其无法再为目前的答案贡献。因此,我们需要维护 \(\operatorname{front}\) 对应的树状数组 \(\operatorname{BIT_f}\),在其中查询 \(b_{j}\) 的后缀和(直到目前的\(\operatorname{bound}\))并在答案中减去该部分贡献。对于 \(b_{j-1}\),我们在 \(\operatorname{BIT_b}\) 中查询其对应前缀和并在答案中加入该部分贡献。需要注意的是,我们需要先删去 \(b_{j}\),再进行查询操作,最后加入 \(b_{j-1}\),这点较显然。最后,判断 \(b_{j}\) 与 \(\operatorname{bound}\) 的关系,累加答案。

放出这部分代码以便理解:

for (int i = 4; i <= n; i++) {
	int bnd = b[i], tmp = 0;
	// tmp: 记录不管4的时候目前情况下的答案 
	frt.clear(), bk.clear();
	frt.add(b[1], 1); 
	for (int j = 3; j < i; j++) bk.add(b[j], 1);
	// 初始化 
	if (b[1] < bnd) tmp += bk.qry(b[1] - 1);
	// 若前面新加入的数小于bnd 
	// 那就把他关于后面目前所有数的贡献加进去 
	if (b[2] > bnd) ans += tmp;
	// 如果贡献可用 就累加 
	for (int j = 3; j < i-1; j++) {
		bk.add(b[j], -1);
		if (b[j-1] < bnd) tmp += bk.qry(b[j-1]-1);
		// 若前面新加入的数小于bnd 
		// 那就把他关于后面目前所有数的贡献加进去 
		if (b[j] < bnd) tmp -= frt.qry(bnd-1) - frt.qry(b[j]);
		// 如果有一个为答案贡献的数离开了 那就从答案中删去它对答案贡献的量 即小于bnd但大于它的数字个数 
		if (b[j] > bnd) ans += tmp;
		// 此时满足排列的条件,可以累加答案 
		frt.add(b[j-1], 1);
	}
}
这篇关于排列 题解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!