zoukankan      html  css  js  c++  java
  • [CSP-S模拟测试]:施工(DP+单调栈+前缀和)

    题目描述

    小$Y$家门前有一条街道,街道上顺序排列着$n$幢建筑,其中左起第$i$幢建筑的高度为$h_i$。
    小$Y$定义街道的不美观度为所有相邻建筑高度差的绝对值之和乘上常数$c$,为了改善街道环境,政府决定进行施工,施工队会选择一些建筑并提升它们的高度,如果一幢建筑最终高度增加了$t$,则需要花费$t_2$的人力。
    小$Y$非常好奇,施工完成后街道的不美观度与施工队花费的人力之和最小为多少。


    输入格式

    第一行包含两个正整数$n,c$。
    接下来一行$n$个正整数,表示每幢建筑的高度。


    输出格式

    输出一行一个整数表示答案。


    样例

    样例输入:

    4 2
    1 3 2 4

    样例输出:

    6


    数据范围与提示

    对于$100\%$的数据,$n,h_i,cleqslant 1,000,000$。
    $ullet Subtask 1(11pts):n,h_ileqslant 300$。
    $ullet Subtask 2(19pts):n,h_ileqslant 1,000$。
    $ullet Subtask 3(23pts):nleqslant 1,000$。
    $ullet Subtask 4(22pts):nleqslant 100,000$。
    $ullet Subtask 5(25pts):$无特殊限制。


    题解

    考场上最后$20$分钟$A$了这道题,所以对于这道题我还是比较清楚的,需要先声名一下,因为我和题解打的不太一样,所以在那天讲这道题的时候可能出现了一些偏差(当初我就看到了题解里有“单调栈”三个字我就以为是一样的),耽误了不少同学的学习时间,可能还会有人怀疑我不是自己打的(确实当时将的有点混乱),在此我向大家表示歉意,并在这篇博客里尽可能详细的说一下我的思路。

    首先,我们肯定会想到$DP$,大多数人会想定义$dp[i][j]$表示到了第$i$幢建筑,当前建筑高为$j$的最小代价。

    转移非常简单,不再赘述,时间复杂度是$Theta(n imes {max(k)}^2)$的,但是可以用数据结构优化成$Theta(n imes max(k))$的。

    但是我们思考,对于一般的非数学题,时间复杂度最多只能减少一次幂,所以这种方式显然是不行的。

    所以我就思考改变$DP$的定义,因为我可以在对后面的点进行决策的时候再改变前面的点的高度,所以我们可以在处理当前点的时候先不改变其高度;因此,我们设$dp[i]$表示到了第$i$幢建筑,当前高度为$h[i]$的最小代价。

    现在$DP$式子设完了,我们考虑如何转移,这也是这道题的第一个难点,至于题解中说的填平,让好多人有了误解。

    因为我们是要从所有的转移点中取一个最小值转移给当前点,而向它转移的点也有很多转移点转移到向它转移的点(不好意思语文不好),例如下图:

    假设我们在考虑给$d$转移时,选择了$b$点,并将$c$点填成红色区域,现在我们考虑转移给$e$点,那么你的的理解可能是填成下图:

    而这样显然是不优的,所以你就会存在疑问,但是我们是要枚举$a,b,c,d$四个点,取最小值填平,于是我们就可能会填成下图:

    那么这个难点就解决了,我们来考虑如何转移:

    列出状态转移方程,假设填平高度为$t$:

    $$dp[i]=min(dp[j]+sum limits_{k=j+1}^{i}{(t-h_k)}^2+c imes abs(h[j]+h[i]-2 imes t)$$

    来解释一下转台转移方程,对于前半部分,也就是暴力人工填,而后半部分注意$2 imes t$是因为你抬高了,所以两边的绝对值都会减。

    这时候你可能会发现$t$要枚举,那你就死了。

    我们考虑拆开式子,就变成了:

    $$sum limits_{k=j+1}^{i}{(t-h_k)}^2=(j-i-1) imes t^2-t imes sum limits_{k=j+1}^{i}h_k+sum limits_{k=j+1}^{i}{h_k}^2$$

    现在你要求出$t$,然后你可能回想,这不就是一个单封函数嘛,三分哇。

    不得不承认,的确可以,但是我们为什么不考虑直接用二次函数对称轴的公式$-frac{b}{2a}$来求呢?

    所以我们的代价最小的$t$即为:$dfrac{2 imes c imes t+sum limits_{k=j+1}^{i}h_k}{2}$。

    因为在求对称轴的时候要除以$2$,而整型自动下取整,所以我们可以先加$0.5$再除以$2$。

    需要注意的是,我们需要将求出的这个$t$与区间内高度最大的建筑取$max$。

    我们可以维护两个前缀和,这样时间复杂度就变成了$Theta(n^2)$了。

    我们接着考虑优化,使用单调栈,保证栈内元素单调递减,并对于每个点取栈内比它小的点作为转移点,下面考虑这样做的正确性。

      首先,求出来的$t$肯定比当前点更小,因为如果填的比它高一定是费力不讨好的,我们不仅需要承担填高中间部分的代价,还要承受绝对值所带来的代价。

      然后,因为$t$还要比区间内的$max(h)$大,所以如果出现下图中的情况,我们在考虑给$c$转移的时候如果还要考虑$asim b$中比$b$小的建筑进行转移的话高度$t$一定是$geqslant h_b$的,所以我们可以直接选择$asim d$中的点向$c$转移,而$bsim c$中的点同理。

    因为对于单调栈,每个点最多只会进栈一次,出栈一次,所以我们就优化成了$Theta(n)$。

    还需要注意的一点是,因为$dp[n]$的含义为到了$n$这个点,高度为$h_n$的最小代价,然而最后一个建筑的高度可以比$h_n$大,所以我们可以考虑将$h_0$设为$+infty$而$h_{n+1}$设为$+infty -1$,这样$dp[n+1]$就是答案了。

    时间复杂度:$Theta(n)$。

    期望得分:$100$分。

    实际得分:$100$分。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    int n,c;
    long long h[1000010];
    int sta[1000010];
    long long dp[1000010];
    long long s[2][1000010];
    long long ans=1LL<<60;
    long long ask(int x,int y,int z)
    {
    	long long len=x-y-1;
    	long long up=s[0][x-1]-s[0][y]+c;
    	long long down=s[1][x-1]-s[1][y]+h[x]*c;
    	if(y)
    	{
    		up+=c;
    		down+=h[y]*c;
    	}
    	long long res=1.0*up/2/len+0.5;
    	res=max(res,h[z]);
    	res=min(res,h[x]);
    	res=min(res,h[y]);
    	res=len*res*res-up*res+down;
    	return res;
    }
    int main()
    {
    	scanf("%d%d",&n,&c);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&h[i]);
    		s[0][i]=s[0][i-1]+(h[i]<<1);
    		s[1][i]=s[1][i-1]+h[i]*h[i];
    	}
    	h[0]=(h[n+1]=1<<30)--;
    	sta[1]=0;sta[0]=1;
    	while(sta[0]&&h[sta[sta[0]]]<=h[1])
    	{
    		dp[1]=min(dp[1],dp[sta[sta[0]-1]]+ask(1,sta[sta[0]-1],sta[sta[0]]));
    		sta[0]--;
    	}
    	sta[++sta[0]]=1;
    	for(int i=2;i<=n;i++)
    	{
    		dp[i]=dp[i-1]+c*abs(h[i]-h[i-1]);
    		while(sta[0]&&h[sta[sta[0]]]<=h[i])
    		{
    			dp[i]=min(dp[i],dp[sta[sta[0]-1]]+ask(i,sta[sta[0]-1],sta[sta[0]]));
    			sta[0]--;
    		}
    		sta[++sta[0]]=i;
    	}
    	ans=dp[n];
    	while(sta[0]&&h[sta[sta[0]]]<=1<<30-1)
    	{
    		long long len=n-sta[sta[0]-1];
    		long long up=s[0][n]-s[0][sta[sta[0]-1]];
    		long long down=s[1][n]-s[1][sta[sta[0]-1]];
    		if(sta[sta[0]-1])
    		{
    			up+=c;
    			down+=h[sta[sta[0]-1]]*c;
    		}
    		long long res=up/2/len+0.5;
    		res=max(res,h[sta[sta[0]]]);
    		res=min(res,h[n+1]);
    		res=min(res,h[sta[sta[0]-1]]);
    		res=len*res*res-up*res+down;
    		ans=min(ans,dp[sta[sta[0]-1]]+res);
    		sta[0]--;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    rp++

  • 相关阅读:
    route-over VS mesh-under
    IOS算法(三)之插入排序
    GitHub学习笔记
    Python-面向对象 (二 继承)
    POJ 3518 Prime Gap(素数题)
    struts2的总体回想(ACTION、拦截器、值栈、OGNL表达式、ModelDriven方案等)
    first move advantage_百度搜索
    【绿茶书情】:《SOHO小报》和《凤…
    潘石屹的SOHO小报猝死
    ASP.NET Hashtable输出JSON格式数据
  • 原文地址:https://www.cnblogs.com/wzc521/p/11576540.html
Copyright © 2011-2022 走看看