题解:AT_agc062_b [AGC062B] Split and Insert
posted on 2024-11-22 03:36:22 | under | source
考虑时光倒流,变为每次选取一个子序列移到末尾,要使得最终序列升序。
考虑最后一次操作,则选取的点集合和没选取的点集合此前应当是内部相对顺序不变的。那么分为两个子问题:让这两个集合内部升序的最小代价。
注意到此题的代价满足乘法分配率,又因为两个集合之间的顺序我们并不关心,所以假如一轮操作同时涉及了两个不同集合,就可以将其拆为两个操作,分别操作了两个集合。
那么两个子问题完全独立。现在简单了,对值域进行区间 dp,\(f_{i,l,r}\) 表示倒流完操作 \([i,k]\),其中 \([l,r]\) 范围内的元素有序的最小花费。初始化 \(f_{k+1,l,r}\) 判断下一开始 \([l,r]\) 是否有序即可,转移是朴素的不再赘述。
复杂度 \(O(n^3k)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define MIN(a, b) a = min(a, b)
const int N = 1e2 + 5;
int n, m, x, a[N], c[N], f[N][N][N], inf;
signed main(){
memset(f, 0x3f, sizeof f), inf = f[0][0][0];
cin >> n >> m;
for(int i = 1; i <= m; ++i) scanf("%lld", &c[i]);
for(int i = 1; i <= n; ++i) scanf("%lld", &x), a[x] = i;
for(int i = 1; i <= n; ++i)
for(int j = i, lst = -1; j <= n; ++j){
if(lst > a[j]) break;
f[m + 1][i][j] = 0;
lst = a[j];
}
for(int i = m; i; --i)
for(int l = 1; l <= n; ++l)
for(int r = l; r <= n; ++r){
MIN(f[i][l][r], f[i + 1][l][r]);
for(int k = l; k < r; ++k)
MIN(f[i][l][r], f[i + 1][l][k] + f[i + 1][k + 1][r] + c[i] * (r - k));
}
if(f[1][1][n] >= inf) cout << -1;
else cout << f[1][1][n];
return 0;
}

浙公网安备 33010602011771号