【杂题总汇】UVa-1336 Fixing the Great Wall

【UVA-1336】Fixing the Great Wall

一开始把题看错了……直接用的整数存储答案;之后用double存最后输出答案的时候取整就AC了😂

⭐小紫薯例题 +vjudge链接-传送门+


 

◇ 题目

<手写翻译>

 派遣一个机器人去修复一条直线上的n个点,机器人有一个速度v(单位/秒),且一开始在坐标x。对于第i个点,若立即修复它,需要花费ci,但是未修复的点会随着时间损坏,修复难度也增加,若第i个点未修复,则每过一秒将会增加Δi的额外花费。

<输入输出>

多组数据。每组数据第一行依次给出修复点的数量n,机器人的速度v以及它一开始的位置x。接下来n行,每行描述一个修复点,依次给出点的坐标,立即修复的花费以及每秒增加的额外花费(输入时点的坐标不一定有序)。

数据以n=0,v=0,x=0结束,不处理该数据。

对于每组数据,输出修复所有点的最小花费(存在浮点数,输出答案时忽略小数位取整)。

<样例&解释>

输入 输出 解释

3 1 1000
1010 0 100
998 0 300
996 0 3
3 1 1000
1010 0 100
998 0 3
996 0 3
0 0 0

2084

1138

对于第一组数据,修复顺序为

1000->998->1010->996

从起始点1000到修复点998,到达时间为2,额外花费600,总花费600;

从998到1010,到达时间为14,额外花费为1400,总花费2000;

从1010到996,到达时间为28,额外花费为84,总花费为2084;

 


 

◇ 解析

由于修复一个点不需要时间,所以最优方案一定不会跳过一个点到达另外一个点去修复,而是每到达一个点就修复,然后到达另一个点。因此机器人走过的点都会被修复——不难发现被修复的点是一个区间!

所以容易想到区间DP。和类似的题一样,状态定义为dp[l][r][k],表示修复了点l~r,且k=0/1在左/右端点时的最小花费。但是注意到每个点花费是随着时间增长的,再把时间储存在状态中显然是不现实的,但是解决方法也很简单——我们可以处理出从当前区间的某个端点u到达要修复的点v的时间内,没有修复的点的花费总共增加了多少,由于u到v的时间固定,我们只需要确定没有修复的点每秒总共增加多少花费,可以用前缀和解决。

举个例子:现在已经修复的区间是3~7,机器人在7修复点,下一步到达2修复点。那么没有修复的点的每秒增加的额外花费inc为 Δ[1~2]+Δ[8~n],计算出从7到2需要的时间tim,则这段时间内产生的额外花费为inc*tim。由于这些额外花费是不可避免的,我们将它一齐存入dp数组里。

那么dp[l][r][k]现在的含义是已经修复l~r,且现在(k=0/1)处于左/右端点时,已经支付的花费和未支付但必定存在的额外花费(也就是没有修复的点的花费已经随着时间增加了,而我们要修复所有点,这些新的额外花费也一定会算入最后的总花费,就可以将这些额外花费直接加入当前的花费,而保持其他点的花费不变)的和。则利用刷表法,可以得到状态转移方程式如下:

只是看起来复杂……🙃

Δ[l~r]可以用前缀和处理,式子中的(Δ[1~i-1]+Δ[j+1~n])表示的是除去已经修复的点i~j外,其他未修复的点每秒增加多少花费;(pos[i]-pos[i-1])等类似的式子计算的是从当前点到目标点的距离,除以速度v,得到需要的时间,与每秒增加花费相乘得到必定支付的额外花费;再加上修复目标点的花费cost。

其他就没有什么麻烦的了~


 

◇ 源代码

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1000;
const double INF=1e17;
struct FIXPOINT{ //需要修复的点
	double pos,del,cst; //位置,每秒增加花费,修复原花费
	bool operator <(const FIXPOINT B)const{return pos<B.pos;}
}fix[MAXN+5];
int n,case_cnt; //点数量,当前数据组数
int vis[MAXN+5][MAXN+5][2]; //vis[i][j][k]:是否计算过dp[i][j][k]
double sum[MAXN+5],dp[MAXN+5][MAXN+5][2]; //前缀和,dp数组
double v,beg_pos; //速度,起始位置
double DP(int L,int R,int k){
	if(L==1 && R==n) return 0; //终止位置,全部修完
	if(vis[L][R][k]==case_cnt) return dp[L][R][k];
	vis[L][R][k]=case_cnt;
	double inc=sum[L-1]-sum[0]+sum[n]-sum[R],tim,pos=k? fix[R].pos:fix[L].pos;
	//inc:每秒增加花费,tim:到达目标点花费时间,pos:当前位置
	double &res=dp[L][R][k]=INF;
	if(L-1>=1) //修复点L-1
	{
		tim=(pos-fix[L-1].pos)/v;
		res=min(res,DP(L-1,R,0)+inc*tim+fix[L-1].cst);
	}
	if(R+1<=n) //修复点R+1
	{
		tim=(fix[R+1].pos-pos)/v;
		res=min(res,DP(L,R+1,1)+inc*tim+fix[R+1].cst);
	}
	return res;
}
int main(){
	//freopen("in.txt","r",stdin);
	while(~scanf("%d%lf%lf",&n,&v,&beg_pos) && n)
	{
		case_cnt++;
		for(int i=1;i<=n;i++)
			scanf("%lf%lf%lf",&fix[i].pos,&fix[i].cst,&fix[i].del);
		sort(fix+1,fix+n+1); //按位置排序
		fix[0].pos=-INF;fix[n+1].pos=INF; //保证i(1<=i<=n)满足pos[i-1]<pos[i]<pos[i+1]
		for(int i=1;i<=n;i++)
			sum[i]=sum[i-1]+fix[i].del; //处理前缀和
		double ans=INF;
		for(int i=0;i<=n;i++)
			if(fix[i].pos<beg_pos && beg_pos<fix[i+1].pos) //起始位置在i和i+1之间
			{
				double inc=sum[n],tim;
				if(i>=1) //若先修点i
				{
					tim=(beg_pos-fix[i].pos)/v; //时间花费
					ans=min(ans,DP(i,i,0)+tim*inc+fix[i].cst); //处理答案
				}
				if(i+1<=n) //先修点i+1,同上
				{
					tim=(fix[i+1].pos-beg_pos)/v;
					ans=min(ans,DP(i+1,i+1,0)+tim*inc+fix[i+1].cst);
				}
				break;
			}
		printf("%lld\n",(long long)(ans));
	}
	return 0;
}

  


The End

Thanks for reading!

- Lucky_Glass

(Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~📃)
posted @ 2018-08-17 09:10  Lucky_Glass  阅读(321)  评论(0编辑  收藏  举报
TOP BOTTOM