题解 P4769 【[NOI2018]冒泡排序】
LittleDino · · 题解
感谢给我讲了这个题的神仙跳瓜
首先看给的提示,我们可以发现在这样的排序方式下,对于每一个数都只向目标位置方向走,不换向。那么对于一个数
根据
先不考虑字典序严格大于
我们记
如果填比
但是直接这样递推是
如图,即从点
首先,如果不考虑与直线不相交,即为走
再模仿
得到
这样就得到
可以发现
回到有限制字典序严格大于
记
-
v = mn (因为
mn 是最小可以填的,所以v 的下界是mn 。)此时,第
i 项不能填mn ,只能大于mx ,故后面n - i 项的填法有\sum_{k = mx + 1}^n f_{i, k} 种(k 为第i 位填的数)。 -
mn < v < mx 此时第
i 位没有可以填的,后面不再存在合法方案。(但是还是要读完) -
v \geqslant mx 此时第
i 位可以填mn 或大于mx 的数,方案数为\sum_{k = mx}^n f_{i, k} 。
问题又来了,怎么求
不用像其他题解上说的要用树状数组,复杂度
完结撒花~
代码
注意数组要开
#include <bits/stdc++.h>
using namespace std;
namespace TYC
{
typedef long long ll;
const int N = 1.2e6, p = 998244353;
int fac[N + 5], inv[N + 5], vis[N + 5];
inline int read()
{
int v = 0, fl = 0, ch = getchar();
while (!isdigit(ch))
fl |= (ch == '-'), ch = getchar();
while (isdigit(ch))
v = v * 10 + ch - '0', ch = getchar();
return fl ? -v : v;
}
inline int qpow(int v, int tim)
{
int ans = 1;
for (; tim; tim >>= 1, v = (ll)v * v % p)
if (tim & 1)
ans = (ll)ans * v % p;
return ans;
}
void init()
{
fac[0] = 1;
for (int i = 1; i <= N; i++)
fac[i] = (ll)fac[i - 1] * i % p;
inv[N] = qpow(fac[N], p - 2);
for (int i = N; i; i--)
inv[i - 1] = (ll)inv[i] * i % p;
}
inline int C(const int n, const int m)
{
return (n < 0 || m < 0 || n < m) ? 0 : int((ll)fac[n] * inv[m] % p * inv[n - m] % p);
}
int n;
inline int F(const int i, const int j)
{
return i <= j && j <= n ? (C(2 * n - i - j - 1, n - i - 1) - C(2 * n - i - j - 1, n - j - 2) + p) % p : 0;
}
void work()
{
init();
int T = read();
while (T--)
{
n = read();
memset(vis, 0, sizeof(int[n + 1]));
int ans = 0, mx = 0, mn = 1, flag = 0, v;
for (int i = 1; i <= n; i++)
{
v = read();
if (flag)
continue;
ans = (ans + (F(i - 1, max(mx, v) + 1) + p) % p) % p;
if (mx > v && v > mn)
flag = 1;
mx = max(mx, v);
vis[v] = 1;
while (vis[mn]) mn++;
}
printf("%d\n", ans);
}
}
}
int main()
{
TYC::work();
return 0;
}
P.S.
upd 2023.8.5 我已经退役很多年了,一次碰巧上洛谷看到有同学提醒组合数写反了,现特为纠正,希望同学们都能看明白。