题目链接:https://atcoder.jp/contests/agc062/tasks/agc062_b

做法

非常有意思的一道题目。

我们考虑一个序列被切割以后进入一种如放的状态,简单来说就是我手里现在有两个序列可以拼起来。

当操作一次后会发现手里的两个序列变成了四个序列,而代价实际上就是原来两个序列裂成两个序列的代价之和(因为代价的计算公式是一次式)。

因此,显然,我们要把原串切成一堆上升序列,而代价都是可以自己独立统计后再加起来的,直接上区间 DP ,时间复杂度:$O(Kn^3)$ 。

无解的充分必要条件:最少的上升序列个数 $>2^K$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 110
using namespace std;
typedef long long LL;
LL dp[N][N][N],C[N];
int n,K,a[N];
int main(){
scanf("%d%d",&n,&K);
for(int i=1;i<=K;i++)scanf("%lld",&C[i]);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
a[x]=i;
}
// for(int i=1;i<=n;i++)printf("%d ",a[i]);
// printf("\n");
memset(dp,20,sizeof(dp));
int cnt=0;
for(int i=1;i<n;i++){
if(a[i]>a[i+1])cnt++;
}
for(int i=1;i<=K;i++)cnt>>=1;
if(cnt){printf("-1\n");return 0;}
for(int i=1;i<=n;i++){
dp[K+1][i][i]=0;
for(int j=i+1;j<=n;j++){
if(a[j]<a[j-1])break;
dp[K+1][i][j]=0;
}
}
for(int t=K;t>=1;t--){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
dp[t][i][j]=dp[t+1][i][j];
for(int k=i;k<j;k++)dp[t][i][j]=min(dp[t+1][i][k]+dp[t+1][k+1][j]+(j-k)*C[t],dp[t][i][j]);
}
}
}
// for(int i=1;i<=K+1;i++){
// for(int j=1;j<=n;j++){
// for(int k=j;k<=n;k++)printf("%d %d %d:%lld\n",i,j,k,dp[i][j][k]);
// }
// }
printf("%lld\n",dp[1][1][n]);
return 0;
}