1. 程式人生 > 實用技巧 >SDOI2008 Sue的小球【費用提前計算dp】

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;
}