Description

定义一个排列中的一段区间是连续的, 当且仅当这个排列中的最大值与最小值的差为这段区间的长度 $-1$。

对于一个长度为 $n$ 的排列, 给定对于 $i \in [1,n]$,以 $i$ 这个位置结尾的最长连续区间的长度 $L_i$。

询问满足条件的排列的个数(不存在输出 -1 )。

Tutorial

Part 1 无解的判定

不难发现,如果存在 $l_1,r_1,l_2,r_2(r_2\geqslant l_1)$, 使得 $[l_1, r_1]$ 和 $[l_2,r_2]$ 都是合法区间,那么 $[l1, r2]$ 也必然为合法区间。

所以存在解等价于 所有区间两两要么相互包含要么不相交, 并且 $L_n = n$。

Part 2 $L = {1,1,\cdots,1,n}$ 时的子问题

令 $f_n$​ 表示满足 $L_i=1\space (i\in [1,n])$ 的长度为 $n+1$ 的排列数量。

使用类似与错位排列的方法求解。

考虑将长度为 $n$ 的排列中每个元素 $+1$,再插入一个 $1$ 来得到一个长度为 $n+1$ 的排列

分两种情况:

  • 原先排列合法。
    此时插入的 $1$ 只要不在 $2$ 的边上即可。
    证明:假设存在一个长度大于 $3$ 的区间不合法,那么由于 $1$ 是里面的最小值,把 $1$ 拿掉以后, 序列的长度和极差都会减 $1$,这样这个区间在原序列中也一定不合法,矛盾。
    对答案的贡献: $n \times f(n)$。

  • 原先排列不合法。
    此时只能有一个极大非法区间。
    证明: 假设存在两个没有交的非法区间,插入一个 1 并不会使两个非法区间同时消失,即不会变得合法。
    设这个极大非法区间为 $[l,l+k-1]$,显然有 $2 \leqslant k \leqslant n-1$。
    对于某个给定的 $k$,这个区间有 $n-k$ 种选取方案。
    这个区间因为插入一个数以后合法,因此方案数就是 $f(l)$。
    然后把这个区间缩成一个点, 相当于序列长度减少了 $k-1$,所以是方案数是长度为 $f(n-k+2)$ 的序列的方案数, 即为$f(n-k+1)$。
    所以对于某一个的 $k$,其对答案的贡献为 $(n-k)f(k)f(n-k+1)$
    这种情况对答案的贡献: $\sum_{2 \leqslant k \leqslant n-1}(n-k)f(k)f(n-k+1)$

所以我们就有递推式:

Part 3 如何快速求 $f$

发现 $n \leqslant 10000$,所以我们并不能暴力递推这个数列。

这个递推形式看上去很像分治FFT,但这里与分治FFT模板有所不同。

按照一般的分治FFT思路,我们在求 $[l,m]$ 区间对 $[m+1,r]$ 的贡献的时候, 是把 $[l,m]$ 和 $[0,r-l]$ 这个区间卷起来,

但在这道题中 $[0,r-l]$ 区间中 $f$ 的值可能仍未确定。

解决办法是把 $f(k)\times f(n-k+1)$ 的贡献算在 $\max(k, n-k+1)$ 上, 而不是一般的 $k$ 上。

这样可以保证卷的区间已经求出。

并且我们注意到 $(n-k)f(k)f(n-k+1) + (k-1)f(n-k+1)f(k) = (n-1)f(k)f(n-k+1)$

所以只要给系数乘上 $n-1$ 即可。

这部分的 code:

// add 表示模意义下的 +=
// mul 是多项是乘法
void cdq(int l, int r)
{
 if (l == r) {
   add(f[l], 1ll * f[l-1] * (l-1) % MOD);
   return;
 }
 int mid = (l + r) / 2;
 cdq(l, mid);
 int len = mid - l + 1;
 static int _a[N*2+10], _b[N*2+10];
 for (int i = l; i <= mid; ++ i) {
   _a[i - l] = 1ll * f[i] * (i-1) % MOD;
   _b[i - l] = f[i];
 }
 mul(_a, _b, len, len);
 for (int i = std::max(l << 1, mid + 1); i <= r; ++ i) {
   add(f[i], _a[i - (l<<1)]);
 }
 if (l != 2) {
   int d = std::min(l - 1, r - l);
   for (int i = 2; i <= d; ++ i)
     _a[i-2] = f[i];
   for (int i = l; i <= mid; ++ i)
     _b[i-l] = f[i];
   ntt::mul(_a, _b, d-1, mid-l+1);
   for (int i = std::max(l+2, mid+1); i<=r; ++i)
     add(f[i], 1ll * _a[i-l-2] * (i-2) % MOD);
 }
 cdq(mid+1, r);
}

Part 4 扩展到原问题

由 Part 1 可知,原问题中的区间一定构成了一个树形结构。

考虑在儿子答案已知的情况下求点 $u$ 的方案数。

由于每个儿子都是一段连续区间,而这些连续区间不能合并。

不难想到把每个儿子表示的区间缩成一个点,这样加上这个点本身, 组合的方案数为 $f(\deg u)$

所以这个点的答案为儿子的方案书之积乘上 $f(\deg u)$

用一个栈维护这个过程即可

这部分的 code:

typedef std::pair<int, int> Inter;
typedef std::pair<Inter, int> Val;
int n = read();
for (int i = 0; i < n; ++ i)
 a[i] = read();
std::stack <Val> st;
for (int i = 0; i < n; ++ i) {
 Inter pos(i-a[i]+1, i);
 int pans = 1;
 int cnt = 0;
 while (!st.empty() && st.top().first.first >= pos.first) {
   pans = 1ll * pans * st.top().second % MOD;
   st.pop();
   cnt++;
 }
 if (!st.empty() && st.top().first.second >= pos.first) {
   puts("0");
   return;
 }
 pans = 1ll * pans * f[cnt] % MOD;
 st.push( Val(pos, pans) );
 printf("%d\n", st.top().second);
}



Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.