CCPC 2020 秦皇岛站 H题
放个codeforces链接 点我
主要参照YaoBIG的题解,这里记录一下题解中没提到的细节
首先是套路的拆贡献,对于每一个$t$,答案为$\left \{ a_i = k \right \} + 2\left \{ a_i = a_j = k (j < i) \right \}$
然后转化为枚举$i$和$(i, j)$统计每一个序列的贡献
先看$\left \{ a_i = k \right \}$中是怎么做的,对于一个位置$i$如果填上$k$,很自然地可以想到分为$k$是首次出现和$k$不是首次出现两种情况。
1、$k$如果是首次出现,那么$1- (i - 1)$的位置一定出现过了$1, 2, \cdots, k - 1$这些数,$i$后面的数可以填$1, 2, 3, 4, \cdots, k$以及$k + 1$,可以首先设$f(i, k)$表示长度为$i$的,已经最大值为$k$的方案数,然后考虑$i$后面的数怎么填 ,可以设$g(i, k)$表示从最大值为$k$的情况开始填,一直填$i$位的方案数,那么第一种情况的答案就是$f(i - 1, k - 1) * g(n - i, k)$。
2、考虑$k$不是第一次出现,(我想不出来)那么$1 - (i - 1)$的位置一定已经出现过了一个$j$满足$a_j$第一个$= k$,发现这样子的情况和1、中几乎完全一致,为 $f(j - 1, k - 1) * g(n - j - 1, k)$
那么$\left \{ a_i = t \right \}$的答案就是$f(i - 1, k - 1) * g(n - i, k) + \sum_{j = 1}^{i - 1}f(j - 1, k - 1) * g(n - j - 1, k)$,外层枚举$k, i$,做个前缀和
然后考虑$\left \{ a_i = a_j = k (j < i) \right \}$怎么做,发现和$j$位置上的$k$是不是第一次出现有关,一个$(j, i)$的答案为$f(j - 1, k - 1) * g(n - j - 1, k) + \sum_{t = 1}^{j - 1}f(t - 1, k - 1) * g(n - t - 2, k)$,$i$对这个答案没啥影响,一个$j$的答案即为一个$(j, i)$的答案直接乘上$(n - j)$
现在考虑$f$和$g$怎么做,$f$的初值应该为$f(0, 0) = f(1, 1) = 1$,这里注意只有$f(0, 0)$代表啥都没有,是唯一一个$f(0, ?)$中的合法状态,如果有了$f(i, j)$,现在填第$i + 1$位,一种可行的填法是填$j + 1$,所以转移到$f(i + 1, j + 1)$,另外一种可行的填法是填$1 - j$中的任意一个数,所以也可以转移到$f(i + 1, j)$;而$g$的初值是$g(0, ?) = 1$,当我们从$g(i, ?)$转移到$g(i + 1, j)$时,我们考虑把之后填的数放到序列的后面,填上一个数当作这个序列的第一位,那么如果第一次填了$j$,是从$g(i, j - 1)$转移来的,如果不是第一次填$j$,则是从$g(i, j)$转移来的。
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef double db; typedef pair <int, int> pin; const int N = 3005; int testCase, n; ll P, ans1[N], ans2[N], ans[N], f[N][N], g[N][N], sum1[N], sum2[N]; namespace Fread { const int L = 1 << 15; char buffer[L], *S, *T; inline char Getchar() { if(S == T) { T = (S = buffer) + fread(buffer, 1, L, stdin); if(S == T) return EOF; } return *S++; } template <class T> inline void read(T &X) { char ch; T op = 1; for(ch = Getchar(); ch > '9' || ch < '0'; ch = Getchar()) if(ch == '-') op = -1; for(X = 0; ch >= '0' && ch <= '9'; ch = Getchar()) X = (X << 1) + (X << 3) + ch - '0'; X *= op; } } using namespace Fread; namespace Fwrite { const int L = 1 << 15; char buf[L], *pp = buf; void Putchar(const char c) { if(pp - buf == L) fwrite(buf, 1, L, stdout), pp = buf; *pp++ = c; } template<typename T> void print(T x) { if(x < 0) { Putchar('-'); x = -x; } if(x > 9) print(x / 10); Putchar(x % 10 + '0'); } void fsh() { fwrite(buf, 1, pp - buf, stdout); pp = buf; } template <typename T> inline void write(T x, char ch = 0) { print(x); if (ch != 0) Putchar(ch); fsh(); } } using namespace Fwrite; inline void inc(ll &x, ll y) { x += y; if (x >= P) x -= P; } int main() { #ifndef ONLINE_JUDGE freopen("sample.in", "r", stdin); #endif read(testCase); for (int _ = 1; _ <= testCase; ++_) { read(n), read(P); for (int i = 0; i <= n; i++) ans[i] = ans1[i] = ans2[i] = 0; for (int i = 0; i <= n; i++) for (int j = 0; j <= n; j++) f[i][j] = g[i][j] = 0; if (P == 1) { printf("Case #%d:\n", _); for (int i = 1; i <= n; i++) printf("%lld%c", ans[i], " \n"[i == n]); continue; } // for (int i = 0; i <= n; i++) g[0][i] = 1; f[0][0] = f[1][1] = 1; for (int i = 1; i < n; i++) for (int j = 1; j <= n; j++) { if (j < n) inc(f[i + 1][j + 1], f[i][j]); inc(f[i + 1][j], f[i][j] * j % P); } for (int i = 0; i <= n; i++) g[0][i] = 1; g[1][n] = n % P; for (int i = 1; i < n; i++) g[1][i] = (i + 1) % P; for (int i = 2; i <= n; i++) for (int j = 0; j <= n; j++) { inc(g[i][j], g[i - 1][j] * j % P); if (j < n) inc(g[i][j], g[i - 1][j + 1]); } // for (int i = 0; i <= n; i++) { // for (int j = 0; j <= n; j++) printf("%lld ", f[i][j]); // printf("\n"); // } // printf("\n"); // for (int i = 0; i <= n; i++) { // for (int j = 0; j <= n; j++) printf("%lld ", g[i][j]); // printf("\n"); // } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { sum1[j] = sum1[j - 1]; if (n - j - 2 >= 0) inc(sum1[j], f[j - 1][i - 1] * g[n - j - 2][i] % P); sum2[j] = sum2[j - 1]; if (n - j - 1 >= 0) inc(sum2[j], f[j - 1][i - 1] * g[n - j - 1][i] % P); inc(ans1[i], 1LL * (n - j) * sum1[j - 1] % P); if (n - j - 1 >= 0) inc(ans1[i], f[j - 1][i - 1] * g[n - j - 1][i] % P * (n - j) % P); inc(ans2[i], sum2[j - 1]); inc(ans2[i], f[j - 1][i - 1] * g[n - j][i] % P); } ans[i] = ans2[i]; inc(ans[i], ans1[i]), inc(ans[i], ans1[i]); } // for (int i = 1; i <= n; i++) // printf("%lld ", ans1[i]); // printf("\n"); // for (int i = 1; i <= n; i++) // printf("%lld ", ans2[i]); // printf("\n"); // printf("Case #%d:\n", _); // for (int i = 1; i <= n; i++) // printf("%lld%c", ans[i], " \n"[i == n]); printf("Case #"), write(_, ':'), puts(""); for (int i = 1; i <= n; i++) write(ans[i], " \n"[i == n]); } return 0; }
时间复杂度$O(n^2)$,要注意常数。