Easy Climb UVA - 12170 滚动dp +离散化+ 单调队列优化

E.Easy Climb

Somewhere in the neighborhood we have a very nice mountain that gives a splendid view over the surrounding area. There is one problem though: climbing this mountain is very difficult, because of rather large height differences. To make more people able to climb the mountain and enjoy the view, we would like to make the climb easier.

To do so, we will model the mountain as follows: the mountain consists of n adjacent stacks of stones, and each of the stacks is hi high. The successive height differences are therefore hi+1-hi (for 1 ≤ i ≤ n-1). We would like all absolute values of these height differences to be smaller than or equal to some number d.

We can do this by increasing or decreasing the height of some of the stacks. The first stack (the starting point) and the last stack (the ending point) should remain at the same height as they are initially. Since adding and removing stones requires a lot of effort, we would like to minimize the total number of added stones plus the total number of removed stones. What is this minimum number?

Input

On the first line one positive number: the number of testcases, at most 100. After that per testcase:
One line with two integers n (2 ≤ n ≤ 100) and d (0 ≤ d ≤ 109): the number of stacks of stones and the maximum allowed height difference.
One line with n integers hi (0 ≤ hi ≤ 109): the heights of the stacks.
Output

Per testcase:
One line with the minimum number of stones that have to be added or removed or “impossible” if it is impossible to achieve the goal.
Sample Input

3
10 2
4 5 10 6 6 9 4 7 9 8
3 1
6 4 0
4 2
3 0 6 3
Sample Output

6
impossible
4

The 2008 ACM Northwestern European Programming Contest

题意:给了n个堆的高度,要求改变堆的高度,首尾不可改变,使得队列的任意相邻的两数之差<=d,求最小代价

题解:

首先,如果数据范围很小的话 可以直接定义dp[i][j]表示处理完前i个高度后最后一个高度为b[j]的最小花费。 这样定义状态正确性是显而易见的。


然而 这里的d范围是10^9 显然不允许我们这么暴力的去枚举每一个状态。
所以需要去剪枝。 观察到最多只有100个高度, 却分布在1e9的范围里
肯定有很多状态是不会被用到的,应该要想到用离散化缩小范围。

首先,考虑只有3个值的情况:h1, h2, h3。那么根据题意,h2的范围应该在区间[h1-d,h1+d]和[h3-d,h3+d]的交集,即h1应该在[max(h1,h3)-d,min(h1,h3)+d]之间。如果这个区间是空集,即abs(h3-h1)>(3-1)*d,那么自然无解,否则:

1、如果h2就在区间内部,那么不需要修改;

2、如果h2<max(h1,h3)-d,那么就修改为交集的区间下界max(h1,h3)-d;

3、如果h2>min(h1,h3)+d,那么就修改为交集区间的上界min(h1,h3)+d。

可以发现,在这个简单的问题中,h2的最优修改方案只有这3种情况。并且我们还发现了,如果要修改,修改后的高度一定是如hp+k*d的形式。---离散化缩小范围

离散化缩小范围后呢,



下面需要想的就是状态转移方程了。

dp[i][j]=min(dp[i-1][k])+abs(x[j]-h[i]);// j-d<=k<=j+d;

一般单调队列有头,尾两个指针来维持单调性,这里只用一个头指针就完成普通单调队列的写法,在本题中 直接把b[] 数组当单调队列来用了
首先

具体做法是:

1.先维护窗口左边界,别让指针k超出了窗口,如果bk] < b[j] - d那么就k++ (因为b数组是从小到大已经排好序的),然后在不超出右边界b[j]+d 的前提下,如果dp[i][k+1] <= dp[i][k],那么k++;

为什么这样是对的的? 好像和之前说的优先队列一点也不一样啊! 其实是一样的操作,仔细回想维护优先队列时是怎么操作的 :用两个指针front、rear 先更新左边界,防止当前位置超出区间边界,一旦超出就front++; 然后每次新加进来一个值就要看看当前队列最右端的元素与新值的大小,如果大于新值那么就rear--,将无用的元素请出队列 ,直到小于新值,就将新值加入, 然而其实上边那个用一个指针的方法是如出一辙的,只不过将删除无用值这一步放到了求最小值时,也就是更新k时

         

#include<iostream>
#include<algorithm>
#include<math.h>
#define ll long long
#define mx 999999999999
using namespace std;
ll a[205],b[1000005],dp[205][1000005];// dp[i][j]表示  处理完前i座山后 最后一座山的高度为b[j]的最小花费;
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,d;
        cin>>n>>d;
        for(int i=0;i<n;i++)
            cin>>a[i];
        if(abs(a[n-1]-a[0])>(n-1)*d)
        {
            cout<<"impossible"<<endl;
            continue;
        }
        ll cnt=0;
        for(int i=0;i<n;i++)//离散化,处理后将山所有可能的改变值存在b数组
        {
            for(int j=0;j<n;j++)
            {
                b[cnt++]=a[i]+j*d;
                b[cnt++]=a[i]-j*d;
            }
        }
        int m;
        sort(b,b+cnt);
        m=unique(b,b+cnt)-b;
        for(int i=0;i<m;i++)//初始化dp
        {
            dp[0][i]=mx;
            if(b[i]==a[0])
                dp[0][i]=0;
        }
        for(int i=1;i<n;i++)
        {
            int k=0;//单调队列头指针
            for(int j=0;j<m;j++)//如果山峰的高度要改变为b[k],那么b[k]的范围为[b[j]-d,b[j]+d],并且要达到最优,要么改变为b[k]-d,要么改变为b[k]+d
            {
                while(k<m&&b[k]<b[j]-d)//改变为区间下界值b[j]-d;找到b[k]>=b[j]-d就退出
                    k++;
                while(k+1<m&&b[k+1]<=b[j]+d&&dp[i-1][k+1]<=dp[i-1][k])//改变为区间上界值b[j]+d
                    k++;
                if(dp[i-1][k]==mx)
                    dp[i][j]=mx;
                else
                    dp[i][j]=dp[i-1][k]+abs(b[j]-a[i]);
            }
        }
        for(int i=0;i<m;i++)
        {
            if(b[i]==a[n-1])
                cout<<dp[n-1][i]<<endl;
        }
    }
}

         

猜你喜欢

转载自www.cnblogs.com/-citywall123/p/11260031.html