[SDOI2008]Sue的小球 题解

Sue的小球 题解

题意

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


题解

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

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

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

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

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

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

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

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

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

\(~~~~\) 注意处理初始值时可以增加一个小球使得它的 \(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\n",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;
}

\(~~~~\)

posted @ 2020-09-17 16:38  Azazеl  阅读(102)  评论(0编辑  收藏  举报