P2466 [SDOI2008]Sue的小球

题目描述

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

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

以Sue的初始位置所在水平面作为\(x\)轴。

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

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

输入输出格式

输入格式:

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

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

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

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

输出格式:

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

说明

对于30%的数据,\(N<=20\)。

对于60%的数据,\(N<=100\)。

对于100%的数据,\(-10^4 <= xi,yi,vi <= 10^4,N < = 1000\)


从暴力开始

我们发现,对于当前时间所处的每一个位置,我们有且仅有两个选择(即向左或向右)

30分到手,\(O(2^N)\)的复杂度。

既然有向左或向右的选择,很容易联想到DP上去啊。

令\(dp[i][j]\)代表已经处理左端为\(i\)号彩蛋和右端为\(j\)号彩蛋时的区间所产生的最大分数。

试试写转移的话,我们会发现两个问题。

  1. 对于当前位置的信息我们需要存储一下,也就是在左端点还是右端点,多开一维维护即可。

  2. 对于彩蛋的分数,是和时间挂钩的,如果想完整的求解,得把时间压进去啊。

时间压进去肯定爆了有没有别的办法呢?

当然有,每一个点的最终得分其实不就等于 它的初始得分 减去 它的损失分 吗

我们尝试维护这样一个损失分的最小值。

对于 每一段转移的时候 我们都能求出 这段时间内 还没有得到的彩蛋的分的 损失值

\(dp[i][j][0]=min(dp[i+1][j][0]+(t[i+1].x-t[i].x)*(sumv[i]+sumv[n]-sumv[j]),\)

\(dp[i+1][j][1]+(t[j].x-t[i].x)*(sumv[i]+sumv[n]-sumv[j]));\)

\(dp[i][j][1]=min(dp[i][j-1][0]+(t[j].x-t[i].x)*(sumv[i-1]+sumv[n]-sumv[j-1]),\)

\(dp[i][j-1][1]+(t[j].x-t[j-1].x)*(sumv[i-1]+sumv[n]-sumv[j-1]));\)

其中,\(sumv[i]\)维护的是速度的前缀和数组。


code:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1002;
double x0,sumv[N],sumy;
int n;
double dp[N][N][2];
double abs(double x) {return x>0?x:-x;}
struct node
{
double x,y,v;
bool friend operator <(node n1,node n2)
{
return n1.x<n2.x;
}
}t[N];
int main()
{
scanf("%d%lf",&n,&x0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j][0]=dp[i][j][1]=1e300;
for(int i=1;i<=n;i++)
scanf("%lf",&t[i].x);
for(int i=1;i<=n;i++)
{
scanf("%lf",&t[i].y);
sumy+=t[i].y;
}
for(int i=1;i<=n;i++)
scanf("%lf",&t[i].v);
sort(t+1,t+1+n);
for(int i=1;i<=n;i++)
sumv[i]=sumv[i-1]+t[i].v;
for(int i=1;i<=n;i++)
dp[i][i][0]=dp[i][i][1]=(abs(t[i].x-x0))*sumv[n];
for(int i=n-1;i>=1;i--)
for(int j=i+1;j<=n;j++)
{
dp[i][j][0]=min(dp[i+1][j][0]+(t[i+1].x-t[i].x)*(sumv[i]+sumv[n]-sumv[j]),
dp[i+1][j][1]+(t[j].x-t[i].x)*(sumv[i]+sumv[n]-sumv[j]));
dp[i][j][1]=min(dp[i][j-1][0]+(t[j].x-t[i].x)*(sumv[i-1]+sumv[n]-sumv[j-1]),
dp[i][j-1][1]+(t[j].x-t[j-1].x)*(sumv[i-1]+sumv[n]-sumv[j-1]));
}
printf("%.3lf\n",(sumy-min(dp[1][n][0],dp[1][n][1]))/1000.0);
return 0;
}

我居然又在离散化(排序)之前求了\(sumv\),还找了许久的错啊。。。

丢人。。


2018.5.21

04-25 19:21