Vijos1451圆环取数[环形DP|区间DP]

Posted Candy?

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vijos1451圆环取数[环形DP|区间DP]相关的知识,希望对你有一定的参考价值。

背景

小K攒足了路费来到了教主所在的宫殿门前,但是当小K要进去的时候,却发现了要与教主守护者进行一个特殊的游戏,只有取到了最大值才能进去Orz教主……

描述

守护者拿出被划分为n个格子的一个圆环,每个格子上都有一个正整数,并且定义两个格子的距离为两个格子之间的格子数的最小值。环的圆心处固定了一个指针,一开始指向了圆环上的某一个格子,你可以取下指针所指的那个格子里的数以及与这个格子距离不大于k的格子的数,取一个数的代价即这个数的值。指针是可以转动的,每次转动可以将指针由一个格子转向其相邻的格子,且代价为圆环上还剩下的数的最大值。

现在对于给定的圆环和k,求将所有数取完所有数的最小代价。

格式

输入格式

输入文件cirque.in的第1行有两个正整数n和k,描述了圆环上的格子数与取数的范围。

第2行有n个正整数,按顺时针方向描述了圆环上每个格子上的数,且指针一开始指向了第1个数字所在的格子。

所有整数之间用一个空格隔开,且不超过10000。

输出格式

输出文件cirque.out仅包括1个整数,为取完所有数的最小代价。

样例1

样例输入1[复制]

6 1
4 1 2 3 1 3

样例输出1[复制]

21

限制

对于20%的数据,n≤10,k≤3;
对于40%的数据,n≤100,k≤10;
对于60%的数据,n≤500,k≤20;
对于100%的数据,n≤2000,k≤500;

时限1s。

提示

如上图所示,第一步不转动指针,取走4、3两个数,代价为7;
第2步指针顺时针转动2格,圆环上最大数为3,代价为6,取走1、2、3两个数,代价为6;
第3步指针顺时针转动1格,代价为1,取走剩下的一个数1,代价为1;
最小代价为7+6+6+1+1=21。

--------------------------------------------------

该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死该死

取数的代价一定,只考虑转移

贪心发现能取就取一定不丢最优解,从1开始,剩下的一定一直是一段区间

然后我就开始做死了f[i][j][0/1]表示剩下[i,j]这段区间,0表示指针在i-k-1处,1表示指针在j+k+1处,调了三四个小时也没调出来

看人家的题解,i和j都来表示左/右“取了几个数”

然而我发现,他们的意思更准确的是左右的指针跳到了哪里

转移如代码,至于哪个>0否则INF,好像是简化了一下情况,不会丢失

该死该死该死

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=2005,INF=1e9;
int n,k,a[N],f[N][N][2],ans=INF,sum=0;
int mx[N][16];
void initRMQ(int n){
    for(int i=1;i<=n;i++) mx[i][0]=a[i];
    for(int j=1;j<=12;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r){
    if(l>r) return 0;
    int k=log(r-l+1)/log(2);
    return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
void dp(){
    f[1][n][0]=f[1][n][1]=0;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n-i;j++){
            if(i==0&&j==0) continue;
            int m=RMQ(i+k+1,n-k-j);
            if(i>0) f[i][j][0]=min(f[i-1][j][0]+m,f[i-1][j][1]+m*(i+j));
            else f[i][j][0]=INF;
            m=RMQ(i+k+2,n-k-j+1);
            if(j>0) f[i][j][1]=min(f[i][j-1][1]+m,f[i][j-1][0]+m*(i+j));
            else f[i][j][1]=INF;
        }
}
int main(int argc, const char * argv[]) {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
    initRMQ(n);
    dp();
    for(int i=1;i<=n;i++) ans=min(ans,min(f[i][n-i][1],f[i][n-i][0]));
    cout<<ans+sum;
    return 0;
}

 

 

以上是关于Vijos1451圆环取数[环形DP|区间DP]的主要内容,如果未能解决你的问题,请参考以下文章

P1005 [NOIP2007 提高组] 矩阵取数游戏(区间dp)

环形区间DP

区间dp与环形dp

[DP 区间 高精]矩阵取数

区间(环形)dp

加分二叉树 vijos1991 NOIP2003第三题 区间DP/树形DP/记忆化搜索