题目大意:给定一个排列 ,求字典序严格大于 的,最长下降子序列长度不超过 的排列个数 的值。
题目等价于:能划分成两个上升子序列的序列个数。
假设在前 个位置中,最大值是 ,我们发现在余下的数中, 的可以随便放置,而 的数就只能从小到大依次放置,构成另一个上升子序列。
先不考虑字典序的问题。设 表示放了 个数,剩下的数中有 个大于当前最大值的方案数。如果新加进来的数小于最大值,则 不变;如果新加进来的数大于最大值,设它是第 个大于最大值的元素,则 会减少 。
于是:
,
。
我们发现这个式子就是个前缀和:
,
。
之后不难推得:
。
接下来考虑字典序限制。考虑到第 位,填入的数字是 ,后面有 个数 之前的最大值。 后面有 个数比它大, 前面有 给数比它小,这两个数组可以用树状数组方便地计算出来。
我们先计算 的情况。首先 ,因为填完第 位后 不可能 。然后此时如果 ,就代表最大的数被填进去了,这意味着我们后面将只能按顺序依次填入,而这个排列的字典序是严格不大于 的,因此就可以退出了。否则,我们就用 更新答案。
接下来考虑 是否可以等于 。如果刚刚 更新了 ,说明 本身 最大值,当然合法;如果 ,就说明 是剩下元素中最小的,填这个数也合法;否则就是乱序填入剩下的数,不合法,直接退出。
时间复杂度 。
#include <cstdio>
#include <cstring>
const int m = 1200000;
const int maxn = 600005;
const int maxm = 1200005;
const int mod = 998244353;
int T, n, a[maxn], b[maxn], c[maxn], bit[maxn];
int fac[maxm], inv[maxm];
void add(int x) {
for (; x <= n; x += x & -x) bit[x]++;
}
int sum(int x) {
int y = 0;
for (; x; x -= x & -x) y += bit[x];
return y;
}
int mpow(int x, int y) {
int z = 1;
for (; y; y >>= 1) {
if (y & 1) z = 1ll * z * x % mod;
x = 1ll * x * x % mod;
}
return z;
}
void prework() {
fac[0] = 1;
for (int i = 1; i <= m; i++) fac[i] = 1ll * i * fac[i - 1] % mod;
inv[m] = mpow(fac[m], mod - 2);
for (int i = m - 1; ~i; i--) inv[i] = 1ll * (i + 1) * inv[i + 1] % mod;
}
int comb(int n, int m) {
return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int solve(int n, int m) {
return !m ? 1 : !(m ^ 1) ? n : (comb(n + m - 1, m) - comb(n + m - 1, m - 2) + mod) % mod;
}
int main() {
prework();
for (scanf("%d", &T); T--; ) {
scanf("%d", &n);
memset(bit, 0, sizeof(bit));
for (int i = 1; i <= n; i++) {
scanf("%d", a + i), add(a[i]);
c[i] = sum(a[i] - 1);
b[i] = n - a[i] - (i - 1 - c[i]);
}
int ans = 0, cnt = n;
for (int i = 1; i <= n; i++) {
bool flag = b[i] < cnt;
if (flag) cnt = b[i];
if (!cnt) break;
ans = (ans + solve(n - i + 1, cnt - 1)) % mod;
if (!flag && c[i] != a[i] - 1) break;
}
printf("%d\n", ans);
}
return 0;
}