[做题记录-计数][AGC024E] Sequence Growing Hard
题目描述
给定 \(n\), \(k\), \(m\) , 问有多少个序列组 \((A_0,A_1,…,A_n)\) 满足:序列 \(A_i\) 的元素个数为 \(i\) ; 所有元素都在 \([1,k]\) 内; \(\forall i\in[0,n)\) , \(A_i\) 是 \(A_{i+1}\) 的子序列且 \(A_i\) 的字典序小于 \(A_{i+1}\).
输出在 \(\bmod \ m\) 意义下的答案.
Solution
又抄题解去了/kk。
为什么这也可以上树啊/kk。
可以发现本质上我们干的事情就是计数一个操作序列, 每次往序列里面加入一个数, 满足后面一个序列的字典序大于前面那个序列。那么每次就是考虑往序列里面某个数的前面放数, 要求放的数\(x\)小于这个数。
但是这个东西直接\(dp\)并不好刻画, 考虑搞一棵操作树。这个树上的节点用一对数来刻画\((id, val)\)。如果我们建立虚节点\((0, 0)\), 那么每次插入的时候就是选择一个\(val < x\)的位置, 在这个点下面挂一个点\((now, x)\)。那么这样构造出来的一棵树直接与原序列对应。直接考虑对树计数即可。
那么考虑设\(dp_{i, j}\)表示\(i\)个节点的树, 根节点权值为\(j\)的树的个数, 有转移:
\[dp_{i, j} = \sum_{p = 1}^{i - 1}\binom{i - 2}{p - 1}\times dp_{i - p, j}\times \sum_{q = j + 1}^kdp_{p, q}
\]
意义就是枚举根节点\(id\)最小的子树的大小, 分配标号以后再枚举这个子树根节点权值, 前缀和优化以后显然可以\(O(n^2k)\)。
/*
QiuQiu /qq
____ _ _ __
/ __ \ (_) | | / /
| | | | _ _ _ | | _ _ / / __ _ __ _
| | | | | | | | | | | | | | | | / / / _` | / _` |
| |__| | | | | |_| | | | | |_| | / / | (_| | | (_| |
\___\_\ |_| \__,_| |_| \__, | /_/ \__, | \__, |
__/ | | | | |
|___/ |_| |_|
*/
#include <bits/stdc++.h>
using namespace std;
class Input {
#define MX 1000000
private :
char buf[MX], *p1 = buf, *p2 = buf;
inline char gc() {
if(p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MX, stdin);
return p1 == p2 ? EOF : *(p1 ++);
}
public :
Input() {
#ifdef Open_File
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
}
template <typename T>
inline Input& operator >>(T &x) {
x = 0; int f = 1; char a = gc();
for(; ! isdigit(a); a = gc()) if(a == '-') f = -1;
for(; isdigit(a); a = gc())
x = x * 10 + a - '0';
x *= f;
return *this;
}
inline Input& operator >>(char &ch) {
while(1) {
ch = gc();
if(ch != '\n' && ch != ' ') return *this;
}
}
inline Input& operator >>(char *s) {
int p = 0;
while(1) {
s[p] = gc();
if(s[p] == '\n' || s[p] == ' ' || s[p] == EOF) break;
p ++;
}
s[p] = '\0';
return *this;
}
#undef MX
} Fin;
class Output {
#define MX 1000000
private :
char ouf[MX], *p1 = ouf, *p2 = ouf;
char Of[105], *o1 = Of, *o2 = Of;
void flush() { fwrite(ouf, 1, p2 - p1, stdout); p2 = p1; }
inline void pc(char ch) {
* (p2 ++) = ch;
if(p2 == p1 + MX) flush();
}
public :
template <typename T>
inline Output& operator << (T n) {
if(n < 0) pc('-'), n = -n;
if(n == 0) pc('0');
while(n) *(o1 ++) = (n % 10) ^ 48, n /= 10;
while(o1 != o2) pc(* (--o1));
return *this;
}
inline Output & operator << (char ch) {
pc(ch); return *this;
}
inline Output & operator <<(const char *ch) {
const char *p = ch;
while( *p != '\0' ) pc(* p ++);
return * this;
}
~Output() { flush(); }
#undef MX
} Fout;
#define cin Fin
#define cout Fout
#define endl '\n'
using ll = long long;
using pii = pair<int, int>;
const int N = 300 + 5;
int P;
inline void pls(int &x, int y) { x += y; if(x >= P) x -= P; }
inline void dec(int &x, int y) { x -= y; if(x < 0) x += P; }
int C[N][N];
void init(int n = 300) {
C[0][0] = 1;
for(int i = 1; i <= n; i ++) {
C[i][0] = 1;
for(int j = 1; j <= i; j ++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
}
}
int n, k;
int dp[N][N], s[N][N];
signed main() {
cin >> n >> k >> P; init();
for(int i = 0; i <= k; i ++) dp[1][i] = 1;
for(int i = k; i >= 0; i --) s[1][i] = (s[1][i + 1] + dp[1][i]) % P;
for(int i = 2; i <= n + 1; i ++) {
for(int j = 0; j <= k; j ++) {
for(int p = 1; p <= i - 1; p ++) {
pls(dp[i][j], 1ll * C[i - 2][p - 1] * dp[i - p][j] % P * s[p][j + 1] % P);
}
}
for(int j = k; j >= 0; j --) s[i][j] = (s[i][j + 1] + dp[i][j]) % P;
}
cout << dp[n + 1][0] << endl;
return 0;
}