题目描述
$visit ext{_}world$发现有下优化问题可以用很平凡的技巧解决,所以他给你分享了这样一道题:
现在有长度为$N$的整数序列${ a_i}$,你需要从中选出$K$个不想叫的连续子区间(可以存在元素不被选),从左到右记它们的和为$s_1,s_2,...,s_k$,我们的优化目标是最大化下述和式:
$$sum limits_{i=1}^{k-1}|s_i-s_{i+1}|$$
你只需要输出这个最大的和即可。
输入格式
第一行两个整数$N,K$,意义如上。
接下来一行$N$个整数,第$i$个数表示$a_i$,保证有$|a_i|leqslant 10^4$。
输出格式
输出一行一个整数,表示答案。
样例
样例输入:
5 3
5 2 4 3 1
样例输出:
12
数据范围与提示
样例解释:
选择$(5),(2,4,3),(1)$三个子段,$|5-9|+|9-1|=12$
数据范围:
对于全部的测试数据,保证$Nleqslant 3 imes 10^4,Kleqslant min(N,200)$。
$ullet$子任务$1$($10$分):$Kleqslant 3$。
$ullet$子任务$2$($30$分):$Nleqslant 400,Kleqslant 50$。
$ullet$子任务$3$($20$分):$Nleqslant 10^3,Kleqslant 100$。
$ullet$子任务$4$($40$分):无特殊限制。
题解
利用贪心思想,这$k$个序列一定是一高一低的,不可能连续三个及以上持续上升或下降。
对于高的序列,其贡献为$+2$;对于低的序列,其贡献为$-2$;而中间也会有一些并不选的状态,其贡献为$0$,而对于这些状态,其接下来会有高或低的序列,为了区分它们,不妨将其称为上升状态$or$下降状态。
考虑$DP$,设$dp[i][j][0/1/2/3]$表示前$i$个数,分成了$j$段,当前状态是高、低、上升、下降状态。
状态转移很简单,需要注意的是对于边界的处理,即$j=1$和$j=K$的情况。
时间复杂度:$Theta(NK)$。
期望得分:$100$分。
实际得分:$100$分。
代码时刻
#include<bits/stdc++.h>
using namespace std;
int N,K;
int w[30001];
int dp[30001][201][4];
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++)
scanf("%d",&w[i]);
memset(dp,-0x3f,sizeof(dp));
for(int i=1;i<=N;i++)dp[i][0][0]=dp[i][0][1]=dp[i][0][2]=dp[i][0][3]=0;
dp[0][0][0]=dp[0][0][1]=dp[0][0][2]=dp[0][0][3]=0;
for(int i=1;i<=N;i++)
{
dp[i][1][0]=max(dp[i-1][1][0],dp[i-1][0][2])+w[i];
dp[i][1][1]=max(dp[i-1][1][1],dp[i-1][0][3])-w[i];
dp[i][1][2]=max(dp[i-1][1][2],dp[i][1][1]);
dp[i][1][3]=max(dp[i-1][1][3],dp[i][1][0]);
dp[i][K][0]=max(dp[i-1][K][0],dp[i-1][K-1][2])+w[i];
dp[i][K][1]=max(dp[i-1][K][1],dp[i-1][K-1][3])-w[i];
dp[i][K][2]=max(dp[i-1][K][2],dp[i][K][1]);
dp[i][K][3]=max(dp[i-1][K][3],dp[i][K][0]);
for(int j=2;j<K;j++)
{
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j-1][2])+2*w[i];
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][3])-2*w[i];
dp[i][j][2]=max(dp[i-1][j][2],max(dp[i][j][1],dp[i-1][j-1][2]));
dp[i][j][3]=max(dp[i-1][j][3],max(dp[i][j][0],dp[i-1][j-1][3]));
}
}
printf("%d",max(dp[N][K][2],dp[N][K][3]));
return 0;
}
rp++