UVALive 4290 Easy Climb(dp + 精简状态 + 优先队列)

题目大意:给你n个数字,让你通过 +1、-1 将这些点的数值改变,使他们相邻的点之间的差 <= d ,问你最小的操作数。

思路:好题啊!如果不考虑数字范围,那么dp方程是很明显的,d[ i ][ j ] = min(d[i - 1][ k ]) ,j - h <= k < j+h,d[ i ][ j ] 表示前 i 个最后第i个数字为 j 的最小操作数。然后它 高度 和 d 的范围是 10^9,这里状态的精简才是这道题目的难点。但其实,这道题目的状态数最多只有 n*n*2 ,具体分析过程可以看这里:http://hi.baidu.com/sunhaowenprime/item/f7a379ba187467f663388e2d,真是又学习了。。。= = ,在深入一下,还可精简点状态,那就是求出h[ i ] 中的最大值、最小值,他们的状态肯定在这两者之间,然后就是用优先队列处理一下,不优化会TLE,时间复杂度O(n^3)。

自己想的时候,dp方程是很简单,但是一看数据范围就懵了,就是想不出怎么离散化,怎么精简状态,也有想到过那篇博客里说的三个位置,但是没有再深入的想下去,感觉凭这个不能优化。。。 = = ,还有话说这个优先队列,先开始自己还不会写,因为它只跟 i - 1有关系,每次更新一个j,队列的元素又不能变动,看了他的,发现他那个队列的插入删除真是妙啊。。 = =

代码如下:

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long lld;

const lld INF = 0x0fffffffffffffff;

const int MAXN = 111;

int n;
lld d;

lld st[MAXN*MAXN<<1];

lld h[MAXN];

lld my_abs(lld a)
{
    if(a<0) return -a;
    else return a;
}

lld dp[MAXN][MAXN*MAXN<<1];

int front,rear;

struct Node
{
    int s;
    lld ret;
}q[MAXN*MAXN<<1];

void add(int s,lld ret)
{
    while(front <= rear && q[rear].ret >= ret) rear--;
    q[++rear].ret = ret;
    q[rear].s = s;
}

lld get(int s)
{
    while(front <= rear && my_abs(st[q[front].s]-st[s]) > d) front++;
    if(front > rear) return INF;
    return q[front].ret;
}

int main()
{
    int _;
    scanf("%d",&_);
    while(_--)
    {
        scanf("%d%lld",&n,&d);
        int tot = 0;
        lld lm = INF,rm = 0;
        for(int i = 1;i<=n;i++)
        {
            scanf("%lld",&h[i]);
            lm = min(lm,h[i]);
            rm = max(rm,h[i]);
        }
        for(int i = 1;i<=n;i++)
        {
            for(int j = -n;j<=n;j++)
            {
                lld tmp = h[i] + d*j;
                if(tmp >= lm && tmp <= rm)
                    st[++tot] = h[i]+d*j;
            }
        }
        sort(st+1,st+1+tot);
        int m = unique(st+1,st+1+tot)-st-1;
        for(int i = 1;i<=m;i++)
            if(st[i] == h[1])
                dp[1][i] = 0;
            else dp[1][i] = INF;
        //for(int i = 1;i<=m;i++)
            //printf("i = %d,st[i] = %lld\n",i,st[i]);
        for(int i = 2;i<=n;i++)
        {
            front = 1;
            rear = 0;
            int c = 1;
            for(int j = 1;j<=m;j++)
            {
                while(c<=m && my_abs(st[j] - st[c]) <= d)
                {
                    add(c,dp[i-1][c]);
                    c++;
                }
                dp[i][j] = get(j) + my_abs(st[j] - h[i]);
                //printf("i = %d,j = %d,d = %lld,st[j] = %lld,get(j) = %lld\n",i,j,dp[i][j],st[j],get(j));
            }
        }
        int x = lower_bound(st+1,st+1+m,h[n])-st;
        lld ans = dp[n][x];
        if(ans >= INF) puts("impossible");
        else printf("%lld\n",ans);


    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值