P2466 [SDOI2008] Sue 的小球

P2466 [SDOI2008] Sue 的小球

Luogu P2466

题目描述

Sue 和 Sandy 最近迷上了一个电脑游戏,这个游戏的故事发在美丽神秘并且充满刺激的大海上,Sue 有一支轻便小巧的小船。然而,Sue 的目标并不是当一个海盗,而是要收集空中漂浮的彩蛋,Sue 有一个秘密武器,只要她将小船划到一个彩蛋的正下方,然后使用秘密武器便可以在瞬间收集到这个彩蛋。然而,彩蛋有一个魅力值,这个魅力值会随着彩蛋在空中降落的时间而降低,Sue 要想得到更多的分数,必须尽量在魅力值高的时候收集这个彩蛋,而如果一个彩蛋掉入海中,它的魅力值将会变成一个负数,但这并不影响 Sue 的兴趣,因为每一个彩蛋都是不同的,Sue 希望收集到所有的彩蛋。

然而 Sandy 就没有 Sue 那么浪漫了,Sandy 希望得到尽可能多的分数,为了解决这个问题,他先将这个游戏抽象成了如下模型:

将大海近似的看做 \(x\) 轴,以 Sue 所在的初始位置作为坐标原点建立一个竖直的平面直角坐标系。

一开始空中有 \(N\) 个彩蛋,对于第 \(i\) 个彩蛋,他的初始位置用整数坐标 \((x_{i}, y_{i})\) 表示,游戏开始后,它匀速沿 \(y\) 轴负方向下落,速度为 \(v_{i}\) 单位距离/单位时间。Sue 的初始位置为 \((x_{0}, 0)\),Sue 可以沿 \(x\) 轴的正方向或负方向移动,Sue 的移动速度是 \(1\) 单位距离/单位时间,使用秘密武器得到一个彩蛋是瞬间的,得分为当前彩蛋的 \(y\) 坐标的千分之一。

现在,Sue 和 Sandy 请你来帮忙,为了满足 Sue 和 Sandy 各自的目标,你决定在收集到所有彩蛋的基础上,得到的分数最高。

输入格式

第一行为两个整数 \(N\), \(x_{0}\) 用一个空格分隔,表示彩蛋个数与 Sue 的初始位置。

第二行为 \(N\) 个整数 \(x_{i}\),每两个数用一个空格分隔,第 \(i\) 个数表示第 \(i\) 个彩蛋的初始横坐标。

第三行为 \(N\) 个整数 \(y_{i}\),每两个数用一个空格分隔,第 \(i\) 个数表示第 \(i\) 个彩蛋的初始纵坐标。

第四行为 \(N\) 个整数 \(v_{i}\),每两个数用一个空格分隔,第 \(i\) 个数表示第 \(i\) 个彩蛋匀速沿 \(y\) 轴负方向下落的的速度。

输出格式

一个实数,保留三位小数,为收集所有彩蛋的基础上,可以得到最高的分数。

样例 #1

样例输入 #1
3 0
-4 -2 2
22 30 26
1 9 8
样例输出 #1
0.000

提示

对于 \(30\%\) 的数据, \(N\leq 20\)

对于 \(60\%\) 的数据, \(N\leq 100\)

对于 \(100\%\) 的数据,\(-10^4 \leq x_{i},y_{i},v_{i} \leq 10^4\)\(N \leq 1000\)

Solution

首先需要知道的是,当经过一个彩蛋时肯定是会将彩蛋捡起来的,因为如果不捡,如果等之后再来捡的话这个彩蛋带来的贡献会减少,反而不如经过就直接捡起(这道题 \(v_i\) 的数据范围应该为非负数,因为如果 \(v_i\) 可以为负,那么可以一直等待彩蛋向上飘,这样的话最大值是无穷大,显然不应该出现)。

因为每个彩蛋在输入时是无序的,所以在处理前应该先把彩蛋按照 \(x\) 为关键字进行排序,以保证收集的彩蛋是连续的。

考虑动态规划,设 \(f[i][j]\)\(i\sim j\) 可以得到的最大分值,但是很快我们可以发现推不出转移方程,因为 \(f[i][j]\) 的来源太多,并且因为时间流逝彩蛋下落带来的损失也是无法计算的,所以这种方法是行不通的。

换一个方向,将 \(f[i][j]\) 设为 \(i\sim j\) 损失的最小值,但也会发现,因为我们不知道此时的人在哪里,所以无法计算具体时间。

因为收集完 \(i\sim j\) 彩蛋的一瞬间,人只可能在区间的最左端或者是最右端上,即区间的端点,因此我们可以设出新的状态转移方程。

\(f[i][j]\) 表示 \(i\sim j\) 彩蛋捡完后人在 \(i\) 时损失的最小值, \(g[i][j]\) 表示 \(i\sim j\) 彩蛋捡完后人在 \(j\) 时损失的最小值。考虑状态的来源,对于 \(f[i][j]\),人只可能从 \(i+1\) 移动一步到达 \(i\) ,或者是从 \(j\) 跨越整个区间到达 \(i\) 。很容易发现,只有这两种来源是可以更新 \(f[i][j]\) 的。据此,可以推导出对于 \(f[i][j]\) 的状态转移方程:

\(f[i][j]=\text{min}\{f[i+1][j]+(Sum(1,i)+Sum(j+1,n))\times (x_{i+1}-x_i),\\ g[i+1][j]+(Sum(1,i)+Sum(j+1,n)\times (x_j-x_i)) \}\)

类似地,也可以推出 \(g[i][j]\) 的状态转移方程。

根据这些信息,代码就很好写出来了。

需要注意的是,因为数据范围的原因, \(f[i][j]\)\(g[i][j]\) 可能会爆 \(\text{int}\) ,因此需要开 \(\text{long long}\)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<limits.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
template<typename T> void read(T &k)
{
 	k=0;
	T flag=1;char b=getchar();
	while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
	while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
	k*=flag;
}
const long long _SIZE=1e3;
struct BALL{
	long long x,y,v;
}ball[_SIZE+5];
bool cmp(BALL x,BALL y)
{
	return x.x<y.x;
}
long long n,sx;
long long f[_SIZE+5][_SIZE+5],g[_SIZE+5][_SIZE+5];
long long sum[_SIZE+5];
long long SumCount(long long l,long long r) {return sum[r]-sum[l-1];}
long long ans=0;
int main()
{
	mem(f,0x7f);
	mem(g,0x7f);
	read(n),read(sx);
	for (long long i=1;i<=n;i++) read(ball[i].x);
	for (long long i=1;i<=n;i++) {read(ball[i].y);ans+=ball[i].y;}
	for (long long i=1;i<=n;i++) read(ball[i].v);
	n++;ball[n].x=sx;
	sort(ball+1,ball+n+1,cmp);
	for (long long i=1;i<=n;i++) sum[i]=ball[i].v+sum[i-1];
	for (long long i=1;i<=n;i++) if (ball[i].x==sx) f[i][i]=g[i][i]=0;
	for (long long len=2;len<=n;len++)
	{
		for (long long i=1;i+len-1<=n;i++)
		{
			long long j=i+len-1;
			f[i][j]=min(f[i][j],f[i+1][j]+(SumCount(1,i)+SumCount(j+1,n))*(ball[i+1].x-ball[i].x));
			f[i][j]=min(f[i][j],g[i+1][j]+(SumCount(1,i)+SumCount(j+1,n))*(ball[j].x-ball[i].x));
			g[i][j]=min(g[i][j],f[i][j-1]+(SumCount(1,i-1)+SumCount(j,n))*(ball[j].x-ball[i].x));
			g[i][j]=min(g[i][j],g[i][j-1]+(SumCount(1,i-1)+SumCount(j,n))*(ball[j].x-ball[j-1].x));
		}
	}
	printf("%.3lf\n",(double)(ans-min(g[1][n],f[1][n]))/1000);
	return 0;
}

posted @ 2022-07-11 15:28  Hanx16Msgr  阅读(38)  评论(0编辑  收藏  举报