题意
守护者拿出被划分为n个格子的一个圆环,每个格子上都有一个正整数,并且定义两个格子的距离为两个格子之间的格子数的最小值。环的圆心处固定了一个指针,一开始指向了圆环上的某一个格子,你可以取下指针所指的那个格子里的数以及与这个格子距离不大于k的格子的数,取一个数的代价即这个数的值。指针是可以转动的,每次转动可以将指针由一个格子转向其相邻的格子,且代价为圆环上还剩下的数的最大值。
现在对于给定的圆环和k,求将所有数取完所有数的最小代价
分析
很难想的dp,看到环的第一反应还是复制一遍接到后面,但是此题的“取物品”的总消耗是恒定不变的,所以可以贪心的证明,每次把指针所指处所有物品取走是最优解(或者说不能更优),所以一开始指针指向起点时,原来的环就变成了一条链。若指针在起点以右,那么下一步的选择只有再向右走一格或者走到左端点,指针在左时同理。
由此可得dp转移方程
设f[i][j][0]表示右端点距起点间隔为i,左端点距起点间隔为j,指针在左时的情况;f[i][j][1]表示右端点距起点间隔为i,左端点距起点间隔为j,指针在右时的情况;
则:f[i][j][1] = min(f[i][j-1][1] + Add, f[i][j-1][0] + Add*(i+j)) //Add = max{a[2+i+k] .... a[n-j-k+1]}
f[i][j][0] = min(f[i-1][j][0] + Add, f[i-1][j][1] + Add*(i+j))//Add = max{a[1+i+k] .... a[n-j-k]}
处理剩余链中最大值时,每次遍历一遍的复杂读太大,故此时用st表或线段树进行维护,由于常数及编码复杂度的原因,此处选择st表。
代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath>
#include<algorithm>
#define INF 1e15+9
using namespace std;
int n, k;
long long f[2005][2005][2];
int a[2005], Ma[2005][20];
void make_st() {
for(int i = 0; i < n; i++)
Ma[i][0] = a[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 0; i + (1<<j) - 1 < n; i++)
Ma[i][j] = max(Ma[i][j-1], Ma[i+(1<<(j-1))][j-1] );
}
long long RMQ(int l,int r)
{
if (l>r) return 0;
int k=(int)log2(double(r-l+1));
return max(Ma[l][k],Ma[r-(1<<k)+1][k]);
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
make_st();
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n-i; j++) {
if(i == 0 && j == 0) continue;
int Add = RMQ(2+i+k, n-j-k+1);
if(j > 0) f[i][j][1] = min(f[i][j-1][1] + Add, f[i][j-1][0] + Add*(i+j));
else f[i][j][1] = INF;
Add = RMQ(1+i+k, n-j-k);
if(i > 0) f[i][j][0] = min(f[i-1][j][0] + Add, f[i-1][j][1] + Add*(i+j));
else f[i][j][0] = INF;
}
}
long long ans = INF;
for(int i = 0; i <= n; i++) ans = min(ans, min(f[i][n-i][0], f[i][n-i][1]));
for(int i = 1; i <= n; i++) ans += a[i];
cout << ans << endl;
return 0;
}