UESTC - 594 我要长高(DP+滚动数组优化+单调队列优化)

    韩父有 N N个儿子,分别是韩一,韩二…韩 N N。由于韩家演技功底深厚,加上他们间的密切配合,演出获得了巨大成功,票房甚至高达 2000 2000万。舟子是名很有威望的公知,可是他表面上两袖清风实则内心阴暗,看到韩家红红火火,嫉妒心遂起,便发微薄调侃韩二们站成一列时身高参差不齐。由于舟子的影响力,随口一句便会造成韩家的巨大损失,具体亏损是这样计算的,韩一,韩二…韩 N N站成一排,损失即为 C× (韩 i i与韩 i+1 i+1的高度差( 1i<N 1≤i<N))之和,搞不好连女儿都赔了.韩父苦苦思索,决定给韩子们内增高(注意韩子们变矮是不科学的只能增高或什么也不做),增高 1 1cm是很容易的,可是增高 10 10cm花费就很大了,对任意韩 i i,增高 H Hcm的花费是 H2 H2.请你帮助韩父让韩家损失最小。


      题意很清楚,但是一上来做的话很容易没有思路,但是如果知道这是一道dp题之后就好入手了很多。很显然的,我们从韩1到韩n计算,韩i子的情况一定与韩i-1子的状况有关,递归关系很明显,这里用dp【i】【j】表示韩i子的身高为j时的结果,可以写出dp公式:dp[i][j]=Min(dp[i][j], (dp[i-1][k]+Abs(j-k)*C+(h[i]-j)*(h[i]-j)));  这里dp【i-1】【k】表示韩i-1子身高为k时的结果(已知),h【i】存的是韩i的身高。看一下这个式子里的未知量:i、j、k,需要三重循环O(n*100*100),超时。但是我还是把它写了下来,因为单论这一部分的dp而言就够一个题了,再加上后面会提到的单调队列优化的话,我一上来实在是看不懂,跨度太大跳不过去,只能自己按部就班慢慢优化出来。另外,我觉得那个100可以稍微优化上一点点,也就是上下限换成h【】的最小最大值,说不定能过的,然而并没有。

      对于找出来的dp公式先去掉Abs()分解开:

        1、韩i比韩i-1高时(j>k)   dp[i][j]=Min(dp[i][j], dp[i-1][k]-k*C+j*C+X);

        2、韩i比韩i-1矮时(j<k)   dp[i][j]=Min(dp[i][j], dp[i-1][k]+k*C-j*C+X);   (X=(h[i]-j)*(h[i]-j))

  然后就是滚动数组+单调队列优化dp了。先单独说下知识点的问题:滚动数组,并不算陌生,之前用过一两次,前天我再看LCS的时候也见到过滚动数组的使用;滚动数组并不算是一个知识点,而是一个工具,优化空间的工具。做dp的的话,方程难找是另一个层次的问题了,可在有了方程的情况下,数组开不出来实在是很难受的,这时候往往就要滚动数组了。这个题中n*100的数组是能开的,但是看的题解上直接就是最优方法,也就学了过来。单调队列优化dp,单调队列是专门练过的,比较熟悉,但是想到优化dp我是很懵逼的,做完了这题后也算是初步了解;就这个题而言,对于韩i子,我们需要的是(1...100)枚举韩i的身高j,然后里面套一层(1...100)枚举韩i-1的身高k并从中找出与j相配的最合适的k(也就是dp[i-1][k]±k*C最小值),而我们观察上面拆分了的两个式子,每一种都情况中的j,所要匹配的k实际上不是全部(1...100),而只是其中的一部分,而两者恰好可以用一个单调队列来维护,具体在代码中体现。两个,分别是二维数组和滚动数组(也是二维,不过只有2,相当于一维了pre[]+now[])两种方案。还有更加精简的,直接用一个最小值minn维护就可以了,不再写了,稍微改动即可。

超时代码如下:

#include<stdio.h>
const int MAX=50010;
const int INF=1e9;
int N, C;
int h[MAX];
int dp[MAX][110];//韩i身高为j时
int Abs(int x)
{
    if(x>0)
        return x;
    return -x;
}
int Min(int x, int y)
{
    if(x>y)
        return y;
    return x;
}
int main()
{
    while(scanf("%d%d", &N, &C))
    {
        int left=1e9, right=0;
        for(int i=1; i<=N; i++)
        {
            scanf("%d", &h[i]);
            if(left>h[i])
                left=h[i];
            if(right<h[i])
                right=h[i];
        }
        for(int i=0; i<=N; i++)//初始化为极大值
        {
            for(int j=0; j<=100; j++)
            {
                dp[i][j]=INF;
            }
        }
        for(int i=left; i<=right; i++)//初始化dp[0][]
            dp[0][i]=0;
        for(int i=1; i<=N; i++)//韩i
        {
            for(int j=h[i]; j<=right; j++)//韩i的身高
            {
                for(int k=h[i-1]; k<=right; k++)//韩i-1的身高
                {
                    dp[i][j]=Min(dp[i][j], (dp[i-1][k]+Abs(j-k)*C+(h[i]-j)*(h[i]-j)));
                    //动规 公式
                }
            }
        }
        int ans=dp[N][right];
        for(int i=left; i<=right; i++)
        {
            if(ans>dp[N][i])
                ans=dp[N][i];
        }
        printf("%d\n", ans);
    }
    return 0;
}

AC代码如下:(二维数组+单调队列优化)

#include<cstdio>
using namespace std;
const int MAX=50010;
const int INF=1e9;
int N, C;
int dp[MAX][110];//韩i身高为j时
int q[MAX];
int Min(int x, int y)
{
    if(x<y)
        return x;
    return y;
}
int main()
{
    while(~scanf("%d%d", &N, &C) && (N+C))
    {
        int head=0, tail=0;
        int x;
        scanf("%d", &x);
        for(int i=0; i<=100; i++)//对韩1 初始化
        {
            if(i<x)
                dp[1][i]=INF;
            else
                dp[1][i]=(i-x)*(i-x);
        }
        for(int i=2; i<=N; i++)//韩2——》韩n
        {
            scanf("%d", &x);
            head=tail=0;
            for(int j=0; j<=100; j++)//韩i比韩i-1高时
            {
                int temp=dp[i-1][j]-j*C;//韩i-1的半个状态
                while(head<tail && temp<q[tail-1])
                    tail--;
                q[tail++]=temp;
                if(j>=x)//求韩i身高为j时
                    dp[i][j]=q[head]+j*C+(x-j)*(x-j);
                else
                    dp[i][j]=INF;
            }

            head=tail=0;
            for(int j=100; j>=0; j--)//韩i比韩i-1矮时
            {
                int temp=dp[i-1][j]+j*C;
                while(head<tail && temp<q[tail-1])
                    tail--;
                q[tail++]=temp;
                if(j>=x)//求韩身高为j时
                    dp[i][j]=Min(dp[i][j], q[head]-j*C+(x-j)*(x-j));
                else
                    dp[i][j]=INF;
            }
        }
        int ans=INF;
        for(int i=0; i<=100; i++)
        {
            ans=Min(ans, dp[N][i]);
        }
        printf("%d\n", ans);
    }
    return 0;
}

AC代码2(滚动数组+单调队列优化)

#include<cstdio>
using namespace std;
const int MAX=50010;
const int INF=1e9;
int N, C;
int dp[2][110];//韩i身高为j时
int q[MAX];
int Min(int x, int y)
{
    if(x<y)
        return x;
    return y;
}
int main()
{
    while(~scanf("%d%d", &N, &C) && (N+C))
    {
        int head=0, tail=0;
        int x;
        scanf("%d", &x);
        int now=1;
        for(int i=0; i<=100; i++)//对韩1 初始化
        {
            if(i<x)
                dp[now][i]=INF;
            else
                dp[now][i]=(i-x)*(i-x);
        }
        for(int i=2; i<=N; i++)//韩2——》韩n
        {
            scanf("%d", &x);
            head=tail=0;
            now=now^1;//now=now%2;  now=1-now;  均可
            for(int j=0; j<=100; j++)//韩i比韩i-1高时
            {
                int temp=dp[now^1][j]-j*C;//韩i-1的半个状态
                while(head<tail && temp<q[tail-1])
                    tail--;
                q[tail++]=temp;
                if(j>=x)//求韩i身高为j时
                    dp[now][j]=q[head]+j*C+(x-j)*(x-j);
                else
                    dp[now][j]=INF;
            }

            head=tail=0;
            for(int j=100; j>=0; j--)//韩i比韩i-1矮时
            {
                int temp=dp[now^1][j]+j*C;
                while(head<tail && temp<q[tail-1])
                    tail--;
                q[tail++]=temp;
                if(j>=x)//求韩身高为j时
                    dp[now][j]=Min(dp[now][j], q[head]-j*C+(x-j)*(x-j));
                else
                    dp[now][j]=INF;
            }
        }
        int ans=INF;
        for(int i=0; i<=100; i++)
        {
            ans=Min(ans, dp[now][i]);
        }
        printf("%d\n", ans);
    }
    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值