排列 题解
作者:互联网
题面
给定一个长度为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);
}
}
标签:排列,cont,题解,operatorname,答案,序列,我们,bnd 来源: https://www.cnblogs.com/joke3579/p/premtt.html