2018年10月10日训练日记

这几天主要看了dp的斜率优化和单调队列优化。

 

单调队列优化dp:

以下部分内容来自大佬(MaxMercer)博客:

例:

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

Input
有若干组数据,一直处理到文件结束。 每组数据第一行为两个整数:韩子数量N(1<=N<=50000)和舟子系数C(1<=C<=100) 接下来N行分别是韩i的高度(1<=hi<=100)。

我们可以看到这道题:dp!怎么d?想怎么d怎么d。。。好吧其实我们可以分析一下————很容易想到在枚举每个儿子的时候,当前儿子的花费都会受到且只受到前一个儿子的影响,可以建一个dp[i][j]表示当前第i个孩子身高为j的情况。状态转移方程为dp[i][j]=min(dp[i-1][k] + abs(j-k)C + (a[i]-j)(a[i]-j)) a[i]是当前枚举人本身的身高,我们知道他们都可以增高,所以你在状态转移的时候,需要枚举前一个人的身高和这个人的身高k和j,a[k]<=k<=100,a[j]<=j<=100(注意只能增高——可能穿了什么恨天高牌增高鞋),这样dp确实可以d,但是不要高兴的太早了——我们来分析一下时间复杂度——我们要枚举每个人,枚举到每个人的时候还要枚举当前人的身高,还有之前人的身高,从给出的Input数据来看,在两秒的时间限制内————超时!怎么办,玫瑰色的人生还没有开始就结束了,我能怎么办,我也很绝望…

不过不用着急,我们可以用单调队列来帮你解决.分析一下这个方程,我们可以看到有一个abs的东西,即是绝对值,所以我们来分析两种情况,一种是前面那个人比当前枚举的人高,一种前面那个人比当前枚举的人矮,这样我们就可以吧绝对值的帽子去掉,建设新农村…好吧优化dp的新办法。我们看一下去掉绝对值的方程.
当第 i 个儿子的身高比第 i-1 个儿子的身高要高时,
dp[i][j]=min(dp[i-1][k] + j*C-k*C + X); ( k<=j ) 其中 X=(x[i]-j)*(x[i]-j)。
当第 i 个儿子的身高比第 i-1 个儿子的身高要矮时,
dp[i][j]=min(dp[i-1][k] - j*C+k*C + X); ( k>=j ).

我们便可以在去掉绝对值的情况下进行分类讨论.首先,我们先令一个二维数组f,f[i-1][k]=dp[i-1][k]-k*C, g[i][j]=j*C+X; 于是dp[i][j] =min (f[i-1][k])+g[i][j]。

在两个取绝对值的式子里面,我们可以看到都需要前一个人的花费求到一个最小值来更新当前状态,那我们可以建造一个单调递增的队列,在枚举第i个人的时候,我们把前一个人(i-1)在不同身高的状态所用的花费做成一个这样的单调队列,这样就不用枚举前一个人的身高(因为我们想要的最小值已经求出来了),所以就大大减少了时间复杂度。

不过这道题其实是可以不用单调队列的,因为我们只需要保存最小值就可以了…用一个Min不断比较保存前一个人的最小花费即可(不过单调队列的思想很重要!)

相信大家对单调队列有了一个初步的了解,说白了,就是少一种枚举,减去一维,直接可以获取单调队列里保存的最优解,但注意,并非所有的dp都可以用单调队列来优化,只有状态转移方程中出现了求min或max的时候才可能可以用,因为这样才符合单调队列里的性质。

以下部分内容来自benTuTuT:

形如dp[i]=max/min(f[k])+g[i]     (k

优化的对象是f[k]

本来 i和k是要用两个for循环来套完,但是在此处,g[i]与k,无关,而f[k]又要滚动取最值,那么就可以在一个for循环中把f[k]得到最值,同时计算当前的g[i],(强调,for循环的遍历顺序要确定,要保证可能与g[i]产生结果的所有f[k]都已计算完毕)将两者的结果存入dp[i]中,就达到了降维的目的。

补充:上述中的k可以当成i来看,因为是与i一同遍历。

一篇比较完整的dp优化总结:戳这里

例题:HDU 3401

解题报告戳这里

 

 

斜率优化dp:(贴了大佬的题解,侵删)

一篇不错的入门讲解:戳这里

一道例题:

APIO 2010 特别行动队 斜率优化DP,讲的非常详细(上面的入门推荐题目中也有)

代码:(侵删)

#include 
#include 
#define ll long long
using namespace std;
int a,b,c,n,l,r,h,t;
ll sum[1000100],que[1000100],x[1000100],f[1000100],s[1000100];
ll calc(ll x){return a*x*x+b*x+c;}
ll q(ll x){return f[x]+a*sum[x]*sum[x]-b*sum[x];}
double rate(ll j,ll k){return (q(j)-q(k))*1.0/(2.0*a*(sum[j]-sum[k]));}
int main()
{
    scanf("%d",&n);
    scanf("%d%d%d",&a,&b,&c);
    for(int i=1;i<=n;i++)
        scanf("%d",&x[i]);
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+x[i];
    for(int i=1;i<=n;i++)
        f[i]=-1e18;
    for(int i=1;i<=n;i++)
    {
        while(l=rate(que[r],i))r--;
                que[++r]=i;
    }
    printf("%lld",f[n]);
}

感觉主要还是把dp状态转移方程化简成斜率的形式,用前缀和做文章。现在只是初步了解了一些基本原理,还有待做题来加强巩固。

你可能感兴趣的:(训练日记)