[AGC043D] Merge Triplets 题解
[AGC043D] Merge Triplets 题解
观察
注意到如果在最终排列 \(P\) 中,存在 \(i, P_i > \max_{j\in [i + 1, i + k]}P_j\),那么在原排列 \(A\) 中一定存在一个 Triplets 中包含 \([P_i, P_{i + 1}, ... ,P_{i + k}]\)。
这是因为如果在某一个时刻 \(P_i\) 成为了所有指针中最小的那个数,并且被写入,下一个时刻写入的 \(P_{i + 1}\) 因为比 \(P_i\) 小,所以一定在 \(P_i\) 同一个 Triplets 的后一个位置,\(P_j\) 同理。
根据这个观察,我们可以把原序列划分为若干段,每一段都形如 \([P_i, P_{i + 1}, ... ,P_{i + k}]\),注意到如果一段的长度超过 \(3\),那就寄了,因为原排列中的 Triplets 最多就 \(3\) 个数。
我们可以把已知在同一段的拼成原排列中的 Triplets。一共就只有三种情况:
- \(1 + 1 + 1\);
- \(2 + 1/1+2\);
- \(3\);
由此可见,\(2\) 的段数必须不多于 \(1\) 的段数,且一段的长度不超过 \(3\),这是一个 \(P\) 合法的充要条件。
DP
考虑用 DP 统计合法排列的方案数。
如果用插入的方式 DP,时间复杂度会起飞,如果我们已知 \(P\) 中的划分,这样的方案数是好统计的,因为对于一个前缀最大值 \(P_i\),它成为一个段开始的方案数占了所有方案数的 \(\dfrac 1{i + k}\) ,令第 \(i\) 段结尾的下标为 \(r_i\) 所以最终答案就是 \(\dfrac{n!}{\prod_i\dfrac{1}{r_i}}\),对这个式子 DP 的同时记录 \(1\) 的段数和 \(2\) 的段数的差。
时间复杂度 \(O(n^2)\)。
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <iostream>
#define x first
#define y second
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 6000 + 10, M = 6000;
int n, mod, f[N][N*2];
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> mod;
f[0][M] = 1;
for(int i = 0; i < n * 3; i ++) {
for(int j = -i; j <= i; j ++) {
int v = f[i][j + M];
if(!v) continue;
f[i + 1][j + 1 + M] = (f[i + 1][j + 1 + M] + v) % mod;
f[i + 2][j - 1 + M] = (f[i + 2][j - 1 + M] + 1ll * v * (i + 1)) % mod;
f[i + 3][j + M] = (f[i + 3][j + M] + 1ll * v * (i + 1) % mod * (i + 2)) % mod;
}
}
int ans = 0;
for(int j = 0; j <= n * 3; j ++)
ans = (ans + f[n * 3][j + M]) % mod;
cout << ans << '\n';
return 0;
}

QwQ
浙公网安备 33010602011771号