第十一届河南省程序设计大赛 C 山区修路 (dp 垫高当前的这个区域的高度 和 或者直接修斜坡公公路,求最小花费)

C 山区修路

 

  • 内存限制:128MB 时间限制:3s Special Judge: No

 

 

题目描述:

SNJ位于HB省西部一片群峰耸立的高大山地,横亘于A江、B水之间,方圆数千平方公里,相传上古的神医在此搭架上山采药而得名。景区山峰均在海拔3000米以上,堪称"华中屋脊"。SNJ是以秀绿的亚高山自然风光,多样的动植物种,人与自然和谐共存为主题的森林生态区。
SNJ处于中国地势第二阶梯的东部边缘,由大巴山脉东延的余脉组成中高山地貌,区内山体高大,高低不平。 交通十分不便。
最近,HB省决定修一条从YC市通往SNJ风景区的高速公路。经过勘测分析,途中需要经过高度分别为H1,H2,……,Hn的N个山区。由于高低不平,除正常的修路开支外,每段还要多出高度差|Hi - Hi-1|*X万元的斜坡费用。Dr. Kong 决定通过填高一些区域的高度来降低总的费用。当然填高也是需要一些费用的。每填高Y单位,需要付出Y2万元费用。
你能否帮Dr. Kong做出一个规划,通过部分填高工程改造,使得总的费用降下来。

 

输入描述:

第一行: T 表示以下有T组测试数据( 1≤ T ≤8 )
对每组测试数据,   
第一行:N  X(2 ≤ N ≤100,000   1≤ X ≤100)
第二行:N个整数,分别表示N个区域的高度Hi( 1<=Hi<=100 , i=1…. n)

输出描述:

对每组测试数据,输出占一行,一个整数,即经过部分填高工程改造后的最少费用。

样例输入:

复制

1
5 2
2 3 5 1 4

样例输出:

15

提示:

 

来源:

河南省第十一届ACM大学生程序设计竞赛

思路:当时比赛时,怎么都想不出来,怎么dp。当时我第一步,想推的两个高度之间怎么去做的,让他们花费小一点,分析花费方程,垫高:y = x^2,若修斜坡 y = (hi - hj) * k,k为题目上的X,画图分析斜率,也就上增加的速度,2 *x = k ,x  = k/2 时,上升相等,当x<k/2时,垫高花费少,x>k/2,时,前k/2的高度,用垫高,x - k/2 的高度,用修斜坡。这样花费最少,但是其实分析到这,对真正做出这道题,是没有用的。

分析一下高度,区域高度只能增加或者不变。

其实知道一共最多有多少种状态  dp[10000][100][100],一共也就这么多种状态  dp[i][j][k]  前i个,高度为j时的最小花费,dp[i][j][k] i表示第几个,j表示当前的高度,k表示i-1的区域的高度,表示是从i-1区域高度为k转移到 i区域高度为j时的最小花费,但是就是这样想的,但是想想三层for循环。没敢写,有些后悔

比赛完回来,想想其实也想错,其实二维的就够了,但是状态数还是那么多,dp[i][j] 存前i个区域的当前区间高度为j时的最小花费。dp[i][j] 一定是由dp[i-1][1~100]  中转移过来的,我就开始按照,我比赛时推的那个斜率开始写了,但是,出现问题了,你这个递推方程的参照物就是 两个相邻区域高度,若垫高i-1的区域的高度,一定会影响前面的dp数组中的推出的值,若垫高当前区域的高度,那么求出的就不是dp[i][j] 的值了(dp[i][j+垫高的高度]),这样好乱或者根本没法写

最终我,我得出了一个方案,就是先垫高,因为你既然要求dp[i][j],若j>a[i],你一定要去从a[i]的高度去垫高高j的高度。所以先垫高,然后状态转移过程中,高度不会变,只加斜坡。

最后就是时间的问题:当for循环次数多的时候,在for中上加上这个单词 register 会快很多。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 100010
#define INF 0x3f3f3f3f
int dp[Max][105];      //dp[i][j]  前i个区域,高度为j时的修路的最小花费。 
int a[Max];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int x,n;
		scanf("%d%d",&n,&x);
		int Ma = 0;
		for(register int i = 1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			Ma = max(Ma,a[i]);
		}
		memset(dp[0],0,sizeof(dp[0]));
		for(register int i = 0;i<=100;i++)
			dp[i][0] = 0;
		for(register int i = 1;i<=n;i++)
		{
			for(register int j = a[i];j<=Ma;j++)
			{	
				dp[i][j] = (j-a[i])*(j-a[i]);   // 先垫高 
				int tt = INF;
				for(register int k = a[i-1];k<=Ma;k++)
					tt = min(dp[i-1][k] + abs(j-k)*x,tt);   // 然后修斜坡 
				dp[i][j] += tt;
			}
		}
		int ans = INF;
		for(register int i = a[n];i<=Ma;i++)
			ans = min(ans,dp[n][i]);
		
		printf("%d\n",ans);
	}
	return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值