[组合数学题单][LibreOJ NOI Round #2]不等关系
某道题的弱化版(从树上搬到链上面去了),但是即使是弱化版也强得离谱......
不知道以前写的都是些什么,把这个文章大改特改了一番,于是有了现在这个东西。
一个部分分算法
对于 \(n\) 比较小的情况,我们可以使用类似排列 DP 的方法,记 \(f(i,j)\) 表示前 \(i\) 个位置,填入了 \([1,i]\) 的数,且最后一个位置是 \(j\) 的方案数。
如果当前位置要填入一个比 \(j\) 小的,假设填入的这个数为 \(x\in [1,j]\),那么,我们让前 \(i\) 个位置的 \([1,i]\) 的数字中 \([j,i]\) 的数字给 \(x\) 挪个位置 —— 所有 \([j,i]\) 的数字都 \(+1\),不难发现,这样整体增加 \(1\) 之后,并不会违反前面位置的大小关系。
这个算法的复杂度是 \(\mathcal O(n^3)\),用前缀和优化可以到达 \(\mathcal O(n^2)\),不过再往下也难走了。
这个算法是基于一般的排列 DP,它无法脱离限制本身(必须拿一维来记录前一个数字填的啥),因此,\(\mathcal O(n^2)\) 的负责度已经和状态复杂度相同,没有办法再进行优化了。
写这个方法是因为,可能有的时候并不能想到下面的做法,这个做法可以算个次级做法。
另一个算法与正解
在说正解之前,先说一个部分分,这个算法比上面稍微劣一些,但是更靠近正解 —— 暴力容斥。
可以对 <
和 >
进行容斥,比如,我们忽略 >
,只考虑有 <
的情况,那么我们算的就是某些 >
的位置变成了任意符号,另外某些 >
的位置被填成 <
的方案数。
更加正式地,设 \(p_i\) 表示不满足第 \(i\) 个 <
的方案数,那么我们算的就是 \(\norm{p_i\cap p_j\cap\cdots}\) 的方案数,但是,目标是计算 \(\displaystyle \norm{\bigcap _{i=1}^m\bar{p_i}}\),其中 \(m\) 为 <
的数量。
由容斥原理,不难得到
我们需要计算 \(\displaystyle \norm{\bigcup_{i=1}^m p_i}\),由于这个并集不是这么容易,考虑继续使用容斥原理转成交集:
考虑交集的具体意义:有些位置强制填入 <
,有些位置任意填数的方案数 —— 这就相当于将 \(1\sim n\) 分成若干没有联系的上升序列计数,这个很简单吧。
因此,总复杂度为 \(\mathcal O(2^n\times n)\).
不过,我们发现这个做法实际上大有前途,它不像最开始的做法,需要结合数列具体填了什么数,而是将他们抽象出来变成了几个上升序列,而我们也不需要使用暴力容斥来计算上面那个柿子。我们只需要计算将原序列划分为若干上升序列的方案数,这显然是可以直接 DP 做的。
具体地,设 \(f(i)\) 表示满足前 \(i\) 个位置的方案数,记录下所有 >
的位置之后就可以直接转移:
其中 \(\mathscr P\) 即所有 >
的位置集合,\(cnt(l,r)\) 表示 \([l,r]\) 中有多少 >
. 边界是 \(f(0)=1\),并且令 \(0\in \mathscr P\),最后答案乘上一个 \(n!\).
这个柿子暴力做的复杂度是 \(\mathcal O(n^2)\) 的,但是还可以进行优化。不妨将上述柿子写得更正式一些:
其中 \(cnt(i)\) 表示前 \(i\) 个位置的 >
数量,我们尝试将其做一点变化:
这个就是 \(A(x)=[s(x)=\texttt{">"}]f(x)\times (-1)^{-cnt(x)}\) 和 \(B(x)=\frac{1}{x!}\) 的卷积再多 一个 \((-1)^{cnt(i-1)}\) 了。由于 \(A(x)\) 和 \(f\) 有关,因此只能使用分治 NTT,复杂度 \(\mathcal O(n\log^2 n)\).
参考代码
这个是 \(\mathcal O(n^2)\) 的。
using namespace Elaina;
const int Maxn = 5000;
const int mod = 998244353;
inline void chkadd(int& x, int y) { if ((x += y) >= mod) x -= mod; }
int fac[Maxn + 5], inv[Maxn + 5], ifac[Maxn + 5];
inline void prelude() {
fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
rep (i, 2, Maxn) {
fac[i] = (int)(1ll * fac[i - 1] * i % mod);
inv[i] = (int)(1ll * (mod - mod / i) * inv[mod % i] % mod);
ifac[i] = (int)(1ll * ifac[i - 1] * inv[i] % mod);
}
}
int f[Maxn + 5], cnt[Maxn + 5], n;
char s[Maxn + 5];
inline void input() {
std::cin >> (s + 1);
n = (int)strlen(s + 1);
s[0] = '>', cnt[0] = 1;
rep (i, 1, n) cnt[i] = cnt[i - 1] + (s[i] == '>');
}
inline void solve() {
f[0] = 1;
rep (i, 1, n + 1) {
repf (j, 0, i) if (s[j] == '>') {
int ss = (int)(1ll * f[j] * ifac[i - j] % mod);
if (cnt[i - 1] - cnt[j] & 1) ss = (mod - ss) % mod;
chkadd(f[i], ss);
}
}
writln(1ll * f[n + 1] * fac[n + 1] % mod);
}
signed main() {
std::cin.tie(NULL)->sync_with_stdio(false);
prelude();
input();
solve();
return 0;
}
这是 \(\mathcal O(n\log^2 n)\) 的
/** @author __Elaina__ */
#include <bits/stdc++.h>
#define USING_FREAD
// #define NDEBUG
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(register int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define repf(i, l, r) for (register int i = (l), i##_end_ = (r); i < i##_end_; ++i)
#define drep(i, l, r) for(register int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#define masdf(...) fprintf(stderr, __VA_ARGS__)
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T& rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T& rhs) { x = std::max(x, rhs); }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI ((char)getchar())
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = (int)(x % 10), x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
template<class T> inline T listMax(const T& x) { return x; }
template<class T, class... Args> inline T listMax(const T& x, const Args&... args) {
return std::max(x, listMax(args...));
}
template<class T> inline T listMin(const T& x) { return x; }
template<class T, class... Args> inline T listMin(const T& x, const Args&... args) {
return std::min(x, listMin(args...));
}
} // namespace Elaina
using namespace Elaina;
const int Maxn = 1e5 * 4; // four times
const int mod = 998244353;
inline int qkpow(int a, int n) {
int ret = 1;
for (; n; n >>= 1, a = 1ll * a * a % mod)
if (n & 1) ret = 1ll * ret * a % mod;
return ret;
}
namespace _poly {
int G[35];
struct _poly_builtin_init {
inline _poly_builtin_init() {
rep (i, 1, 30) G[i] = qkpow(3, mod - 1 >> i);
}
} _init_func;
int rev[Maxn + 5], n, invn;
inline void init(int len) {
for (n = 1; n < len; n <<= 1);
repf (i, 0, n) rev[i] = (rev[i >> 1] >> 1) | ((i & 1)? n >> 1: 0);
invn = qkpow(n, mod - 2);
}
inline void ntt(std::vector<int>& f, int op = 1) {
f.resize(n);
repf (i, 0, n) if (i < rev[i]) std::swap(f[i], f[rev[i]]);
for (int p = 2, lev = 1; p <= n; p <<= 1, ++lev) {
int len = p >> 1, w = G[lev];
for (int k = 0; k < n; k += p) {
int buf = 1, tmp;
repf (i, k, k + len) {
tmp = (int)(1ll * f[i + len] * buf % mod);
f[i + len] = (f[i] + mod - tmp) % mod;
f[i] = (f[i] + tmp) % mod;
buf = (int)(1ll * buf * w % mod);
}
}
}
if (op != 1) {
std::reverse(f.begin() + 1, f.end());
repf (i, 0, n) f[i] = (int)(1ll * f[i] * invn % mod);
}
}
} // namespace _poly
int fac[Maxn + 5], inv[Maxn + 5], ifac[Maxn + 5];
inline void prelude() {
fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
rep (i, 2, Maxn) {
fac[i] = (int)(1ll * fac[i - 1] * i % mod);
inv[i] = (int)(1ll * (mod - mod / i) * inv[mod % i] % mod);
ifac[i] = (int)(1ll * ifac[i - 1] * inv[i] % mod);
}
}
int n;
char s[Maxn + 5];
int cnt[Maxn + 5];
std::vector<int> f, A, B;
#define sign(i) (((i) & 1)? (-1): (1))
void solve(int l, int r) { // [l, r)
if (l + 1 == r) {
if (l == 0) f[l] = 1;
else f[l] = (mod + 1ll * sign(cnt[l - 1]) * f[l]) % mod;
return ;
}
int mid = l + r >> 1, len = r - l;
solve(l, mid), _poly::init(len << 1);
A.clear(), B.clear();
A.resize(_poly::n), B.resize(_poly::n);
repf (i, 0, mid - l) {
if (s[i + l] == '>')
A[i] = (mod + 1ll * sign(cnt[i + l]) * f[i + l]) % mod;
else A[i] = 0;
}
repf (i, 0, len) B[i] = ifac[i];
_poly::ntt(A), _poly::ntt(B);
repf (i, 0, _poly::n) A[i] = (int)(1ll * A[i] * B[i] % mod);
_poly::ntt(A, -1);
repf (i, mid - l, r - l) f[l + i] = (f[l + i] + A[i]) % mod;
solve(mid, r);
}
signed main() {
std::cin.tie(NULL)->sync_with_stdio(false);
std::cin >> (s + 1);
prelude();
n = strlen(s + 1);
s[0] = '>', cnt[0] = 1;
rep (i, 1, n) cnt[i] = cnt[i - 1] + (s[i] == '>');
_poly::init(n + 2);
f.resize(_poly::n);
solve(0, _poly::n);
writln(1ll * f[n + 1] * fac[n + 1] % mod);
return 0;
}
/**
*
* @warning
* 1. pay attention to the size of static arrays;
* 2. when change the array with type int to long long;
* check the calculation of memories(functions like memset,memcpy and so on);
* 3. whether the function have returned;
*
*/