[SDOI2008]Sue的小球 题解

Sue的小球 题解

题意

(~~~~) 给了 (n) 个小球,第 (i) 个小球在 (x_i) 初始分值为 (y_i) ,同时它会以 (dfrac{v_i}{1000}/ exttt{单位时间}) 的速度降低。(可以降低为负数)从初始位置开始,每个单位时间可以移动 (1) 单位长度。收集小球不需要时间,求收集所有小球后能获得的最大分值。


题解

(~~~~) 首先,可以想到对每个球按位置从小到大排序。由于总的得分固定,我们可以求最小损失,然后考虑:对于两个小球 (l,r (x_l<x_r)) ,若它们都被收集了,则小球 (xin[l,r]) 也被收集一定是最优方案:

(~~~~) 证明:显然。 每个小球被收集,一定是从起始点走到过该小球的,则此时顺手收集路上的小球一定不会使答案变劣,因为收集不需要时间。

(~~~~) 所以,我们考虑已经收集了 ([l,r]) 的小球时,下一步一定是扩展到 (l-1)(r+1) ,不难想到定义 ( exttt{DP}) 状态:令 (dp_{i,j}) 表示已经收集 ([l,r]) 的所有小球时的最小损失。考虑 ( exttt{DP}) 转移时需要当前的位置,因此增加一维,用来记录最后停留在 (i) 或者 (j) (当然也可以定义两个数组)。

(~~~~) 发现还是有问题, ( exttt{DP}) 转移还需要记录当前的时间,可是如果我们把时间也加在 ( exttt{DP}) 维度上,时间就爆了。

(~~~~) 这时就轮到这道题的精髓了——提前计算。我们是可以算出每一次转移时的时间的,那如果我们把每次转移时其他小球损失的分数也加上去,既然每个小球都将会被收集,那么最后的效果其实是一样的。

(~~~~) 具体来说,如果有小球 ([l,r]) 已经被收集,则下一次转移时 ([1,l))((r,n]) 的所有会被消耗的分数都可以直接加在下一次。

(~~~~) 若设:(pre_{i,j}) 表示 (sum_{k=i}^j v_k) ,则有 ( exttt{DP}) 转移方程。

[Large left{egin{array}{l} dp_{i,j,0}=min(~dp_{i+1,j,0}+(x_{i+1}-x_i) imes(pre_{1,n}-pre_{i+1,j})~,~dp_{i+1,j,1}+(x_j-x_i) imes (pre_{1,n}-pre_{i+1,j})~)\ dp_{i,j,1}=min(~dp_{i,j-1,0}+(x_j-x_i) imes(pre_{1,n}-pre_{i,j-1})~,~dp_{i,j-1,1}+(x_j-x_{j-1}) imes(pre_{1,n}-pre_{i,j-1})~) end{array} ight. ]

(~~~~) 然后转移就好了。

(~~~~) 注意处理初始值时可以增加一个小球使得它的 (x=x_0,y=0,v=0) ,初始化时就令这个小球为 (0) 即可。


代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
struct node{
	int x,y,v;
}b[1005];
inline bool cmp(node x,node y) {return x.x<y.x;}
ll dp1[1005][1005],dp2[1005][1005],w[1005];
inline int Abs(int x){return x>0?x:-x;}
int calc(int l,int r){return w[r]-w[l-1];}
int main() {
	int n,x;
	ll tot=0;
	read(n);read(x);
	for(int i=1;i<=n;i++) read(b[i].x);
	for(int i=1;i<=n;i++) read(b[i].y),tot+=b[i].y;
	for(int i=1;i<=n;i++) read(b[i].v);
	b[++n].x=x;
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) dp1[i][j]=dp2[i][j]=1e18;
	}
	for(int i=1;i<=n;i++)
	{
		w[i]=w[i-1]+b[i].v;
		if(b[i].v==0&&b[i].x==x) dp1[i][i]=dp2[i][i]=0;
	}
	//dp1[i][j] 收集了第 i个 至 第 j个彩蛋 最后停留在 i
	//dp2[i][j]··················· j
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			dp1[i][j]=min(dp1[i][j],dp1[i+1][j]+Abs(b[i+1].x-b[i].x)*(calc(1,i)+calc(j+1,n)));
			dp1[i][j]=min(dp1[i][j],dp2[i+1][j]+Abs(b[ j ].x-b[i].x)*(calc(1,i)+calc(j+1,n)));
			dp2[i][j]=min(dp2[i][j],dp1[i][j-1]+Abs(b[ i ].x-b[j].x)*(calc(1,i-1)+calc(j,n)));
			dp2[i][j]=min(dp2[i][j],dp2[i][j-1]+Abs(b[j-1].x-b[j].x)*(calc(1,i-1)+calc(j,n)));
		}
	}
//	printf("%d %d %d
",tot-min(dp1[1][n],dp2[1][n]),dp1[1][n],dp2[1][n]);
	printf("%.3lf",(tot-min(dp1[1][n],dp2[1][n]))/1000.0);
	return 0;
}

(~~~~)

原文地址:https://www.cnblogs.com/Azazel/p/13685995.html