SDOI2008 Sue的小球【费用提前计算dp】
解析
首先,你经过一个地方,肯定就会把这个地方的球搞下来,不然你之后再来拿这个球,得分就变低了,不划算。所以你在某个时间拿到的球的集合是一段连续的区间。
然后你搞完了一个区间肯定是为了再次搞别的区间才会走,而不会平白无故地往已经搞过的区间里面跑,所以你搞完一个区间之后停在区间端点。
所以可以考虑定义\(f[0/1][i][j]\)表示当前在左/右端点拿了\([i,j]\)球的最大得分。
转移的时候就要用到提前计算费用的思想了。由于当前段的得分损耗与从开始到现在经历的时间息息相关,而在状态中再加上一维时间是不现实的。我们发现其中的主要矛盾在于曾经的花费时间会对当前的得分情况产生影响,球\(i\)的价值随时间变化的关系是\(y_i-t\times v_i\),而\(t\)是之前的总时间。换个角度想,我们在之前每\(cover\)一段路,相应的\(t\)都会变化,只要还没有把球打下来,就会源源不断地产生损耗,而这个关系是一次函数,可以直接累加。
也就是我们在转移的时候,把这个区间之外的损耗也减去,而且后期无论怎样转移,这个损耗都是跑不掉的,必须要减,所以对选择答案没有影响。而真正打下这个球的时候,因为之前已经把损耗算上了,所以直接加\(y_i\)就可以了。
我们就可以得到转移方程式:
\(f[0][i][j]=max(f[0][i+1][j]-(a[i+1].x-a[i].x)\times(s[i]+s[n]-s[j]),f[1][i+1][j]-(a[j].x-a[i].x)\times(s[i]+s[n]-s[j]))+a[i].y\)
\(f[1][i][j]=max(f[1][i][j-1]-(a[j].x-a[j-1].x)\times(s[i-1]+s[n]-s[j-1]),f[0][i][j-1]-(a[j].x-a[i].x)\times(s[i-1]+s[n]-s[j-1]))+a[j].y\)
►Code View
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 1005
#define INF 0x3f3f3f3f
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f*x;
}
struct node{
int x,y,v;
}a[N];
int x0,n;
int f[2][N][N];//f[0/1][i][j] 当前在左/右端点 拿了[i,j]球的最大价值
int s[N];//v的前缀和 区间[i,j]以外失去的价值是 时间*(s[i-1]+s[n]-s[j])
bool cmp(node p,node q)
{
return p.x<q.x;
}
int Abs(int x)
{
if(x>0) return x;
return -x;
}
int main()
{
n=rd(),x0=rd();
for(int i=1;i<=n;i++)
a[i].x=rd();
for(int i=1;i<=n;i++)
a[i].y=rd();
int sumv=0;
for(int i=1;i<=n;i++)
a[i].v=rd(),sumv+=a[i].v;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
f[0][i][i]=f[1][i][i]=a[i].y-Abs(x0-a[i].x)*sumv;//注意初始化
for(int i=1;i<=n;i++)
s[i]=s[i-1]+a[i].v;
for(int len=1;len<=n;len++)
for(int i=1;i+len<=n;i++)
{
int j=i+len,tmp=s[i]+s[n]-s[j]/*[i+1,j]以外的损失*/;
f[0][i][j]=max(f[0][i+1][j]-(a[i+1].x-a[i].x)*tmp,f[1][i+1][j]-(a[j].x-a[i].x)*tmp)+a[i].y;
tmp=s[i-1]+s[n]-s[j-1];//[i,j-1]以外的损失
f[1][i][j]=max(f[1][i][j-1]-(a[j].x-a[j-1].x)*tmp,f[0][i][j-1]-(a[j].x-a[i].x)*tmp)+a[j].y;
}
printf("%.3f\n",max(f[1][1][n],f[0][1][n])/1000.0);
return 0;
}
►Code View Ver.2 对损耗进行dp
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 1005
#define INF 0x3f3f3f3f
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f*x;
}
struct node{
int x,y,v;
}a[N];
int x0,n,ans;
int f[2][N][N];//f[0/1][i][j] 当前在左/右端点 拿了[i,j]球的失去的最小价值
int s[N];//v的前缀和 区间[i,j]以外失去的价值是 时间*(s[i-1]+s[n]-s[j])
bool cmp(node p,node q)
{
return p.x<q.x;
}
int main()
{
n=rd(),x0=rd();
memset(f,INF,sizeof(f));
for(int i=1;i<=n;i++)
a[i].x=rd();
for(int i=1;i<=n;i++)
a[i].y=rd(),ans+=a[i].y;
for(int i=1;i<=n;i++)
a[i].v=rd();
a[++n].x=x0;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
if(a[i].x==x0)
{
f[0][i][i]=f[1][i][i]=0;
break;
}
for(int i=1;i<=n;i++)
s[i]=s[i-1]+a[i].v;
for(int len=1;len<=n;len++)
for(int i=1;i+len<=n;i++)
{
int j=i+len,tmp=s[i]+s[n]-s[j]/*[i+1,j]以外的损失*/;
f[0][i][j]=min(f[0][i+1][j]+(a[i+1].x-a[i].x)*tmp,f[1][i+1][j]+(a[j].x-a[i].x)*tmp);
tmp=s[i-1]+s[n]-s[j-1];//[i,j-1]以外的损失
f[1][i][j]=min(f[1][i][j-1]+(a[j].x-a[j-1].x)*tmp,f[0][i][j-1]+(a[j].x-a[i].x)*tmp);
}
printf("%.3f\n",(ans-min(f[0][1][n],f[1][1][n]))/1000.0);
return 0;
}