[NOIP 2024 模擬2]陣列操作
題意
有 \(n + 2\) 個整數 \(a_0, a_1, . . . , a_n, a_{n+1}\),\(a_0 = a_{n+1} = 0\)。你需要做確切地 \(n\) 次操作,每次陣列操作為以下形式: 選擇一個整數 \(x\) 滿足 \(a_x \ne 0\),使得 \(a_x = 0\),令 \(l=\max_{i<x,a_i=0} i,r=\min_{i>x,a_i=0} i\),此次操作的花費為 $\max(a_l, a_{l+1}+, . . . , a_{x−1}) + \max(a_{x+1}. . . , a_{r−1}, a_r) $牛幣。 有多少不同的操作方式使得操作花費的牛幣最少,兩種操作不同當且僅當兩種操 作的操作序列不同。 答案對 \(998244353\) 取模。
思路
發現把一個位置變成 \(0\) 後左右兩部分就沒有關係了,可以使用區間 dp。
定義 \(f_{i,j}\) 表示操作區間 \([i,j]\) 的最小代價,\(g_{i,j}\) 表示操作區間 \([i,j]\) 代價最小的方案數。
\(f_{i,j} = \min(f_{i,k-1}+f_{k+1,j}+\max_{i\le t<k}a_t+\max_{k< t\le j} a_t)\)
\(g_{i,j}=\sum_{k\rightarrow (i,j)} g_{i,k-1}\times g_{k+1,j} \times C_{j-i}^{k-i}\)
第一個轉移方程顯然。第二個轉移方程的 \(C_{j-i}^{k-i}\) 表示這個區間還剩下 \(j-i\) 個操作(按順序),分 \(k-i\) 個給左邊,剩下的給右邊,所以 \(C_{j-i}^{j-k}\) 也正確。可能會有疑問,只確定了哪些操作給左邊,哪些操作給右邊,但內部的順序沒有確定啊?實際上,雖然區間 dp 是從小區間合併成大區間,但實際操作時是從大區間分成小區間,這裡只用確定哪些分給左邊,哪些分給右邊,它們內部的順序它們自己分的時候會確定好。
程式碼
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 998244353;
const int N = 2e3 + 5;
int n, a[N], pre[N], suf[N];
ll cnt[N][N], dp[N][N], c[N][N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
c[0][0] = 1;
for (int i = 1; i <= n; i ++) {
c[i][0] = 1;
for (int j = 1; j <= n; j ++) {
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
c[i][j] %= mod;
}
}
for (int i = 0; i <= n + 1; i ++)
for (int j = 0; j <= n + 1; j ++) cnt[i][j] = 1;
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
int j = i + k - 1;
if (j > n) break;
pre[i - 1] = 0, suf[j + 1] = 0;
for (int t = i; t <= j; t ++) pre[t] = max(pre[t - 1], a[t]);
for (int t = j; t >= i; t --) suf[t] = max(suf[t + 1], a[t]);
dp[i][j] = 1e9, cnt[i][j] = 0;
for (int t = i; t <= j; t ++)
dp[i][j] = min(dp[i][j], dp[i][t - 1] + dp[t + 1][j] + pre[t - 1] + suf[t + 1]);
for (int t = i; t <= j; t ++)
if (dp[i][t - 1] + dp[t + 1][j] + pre[t - 1] + suf[t + 1] == dp[i][j])
cnt[i][j] += cnt[i][t - 1] * cnt[t + 1][j] % mod * c[j - i][j - t] % mod, cnt[i][j] %= mod;
}
}
cout << cnt[1][n] << "\n";
}
signed main() {
freopen("arr.in", "r", stdin);
freopen("arr.out", "w", stdout);
int Case = 1;
// cin >> Case;
while (Case --)
solve();
return 0;
}