【jzoj 1397】圆环取数 {区间动态规划}

题目

这里写图片描述


解题思路

**首先,拿去数字的代价是一定的,也是所有数字的和,这道问题即求解就是最小化转动的代价。由于跟代价相关是剩下数字的最大值,那么如果能拿去一个数环上数将其拿去只会减少接下来转动的代价而不会增加(可能不变),所以能拿的数尽量先拿走,如果要将环上的数分为两段,那么肯定会路过一边的所有数字,即可以先拿走这些数字。在一开始拿去2k+1个数后,环上的数一直为链不会使答案更差。同理,如果拿去链边上k(k>1)个数,那么可以分别一个一个拿去答案也不会更差。这样一开始拿去2k+1个数后,就转化为k=0,n=n-2k-1的问题。贪心同NOIP2007的game一样是不可行的,类似的DP可以解决这道问题。
在转化后,f[i][j][0..1]表示链上剩下ai..aj数字,指针在最左边/右边数字相邻空格最小代价。**

**m[i][j]是i到j之间的最大数,f[i][j][0/1]f[i][j][0/1]表示只剩下i∼ji∼j的区间时指针在左/右边距离k。
f[i][j][0]=minn(f[i-1][j][0]+m[i-1][j],f[i-1][j][1]+(n-k)*m[i-1][j],f[i][j+1][1]+(n-k+1)*m[i][j+1]);
f[i][j][1]=minn(f[i][j+1][1]+m[i][j+1],f[i][j+1][0]+(n-k)*m[i][j+1],f[i-1][j][0]+(n-k+1)*m[i-1][j]);
其中以f[i][j][0]为例:
f[i-1][j][0]+m[i-1][j]是把左指针往右移至j
f[i-1][j][1]+(n-k)*m[i-1][j]是把右指针往左移至j
f[i][j+1][1]+(n-k+1)*m[i][j+1]是把右指针往左移至j+1
剩下的f[i][j][1]只需反过来则行**


代码

#include<algorithm>
#include<cstdio>
using namespace std; 
int n,kk,t,a[2011],ans,answ,m[2011][2011],f[2011][2011][2]; 
int minn(int x,int y,int z)
{ return min(x,min(y,z));}
int main()
{
    scanf("%d%d",&n,&kk); 
    n=n-2*kk-1; 
    for (int i=-kk;i<=n+kk;i++)
    {
      scanf("%d",&t); 
      answ+=t; 
      if (i>=1&&i<=n) a[i]=t; 
    }
    for (int i=1;i<=n;i++)
    {
        int maxs=0; 
        for (int j=i;j<=n;j++)
         {
            maxs=max(maxs,a[j]); 
            m[i][j]=maxs; 
         }
    }

    ans=1e9; 
    for (int k=n-1;k>=1;k--)
    {
        f[0][k][0]=f[0][k][1]=1e9; 
        f[n-k+1][n+1][0]=f[n-k+1][n+1][1]=1e9; 
        for (int i=1;i<=n-k+1;i++)
        {
            int j=i+k-1;
            f[i][j][0]=minn(f[i-1][j][0]+m[i-1][j],f[i-1][j][1]+(n-k)*m[i-1][j],f[i][j+1][1]+(n-k+1)*m[i][j+1]);
            f[i][j][1]=minn(f[i][j+1][1]+m[i][j+1],f[i][j+1][0]+(n-k)*m[i][j+1],f[i-1][j][0]+(n-k+1)*m[i-1][j]);
        }
    }
    for (int i=1;i<=n;i++)
      ans=min(ans,min(f[i][i][0],f[i][i][1])+a[i]+answ);
    printf("%d",ans);
}

猜你喜欢

转载自blog.csdn.net/qq_39897867/article/details/80946796
今日推荐