P4870 [BalticOI 2009 Day1]甲虫 题解
简要题意
在一个数轴上有 \(n\) 滴露水,每滴露水初始水量为 \(m\),每秒会蒸发一滴水,一个甲虫初始在原点,速度为 1,水能瞬间喝完,问它最多能喝到几滴水。
题目分析
对于这种移动区间连续的题目,我们首先考虑区间 dp,记 \(f_{l,r,0}\) 表示喝完区间 \([l,r]\) 的水且在左边表示的最小时间,第三维为 1 的时候在右边,据此设计转移。
但是我们发现本题中多了一维时间,强行增加维度时间复杂度会爆,我们考虑控制变量。
可以发现,上述转移最大的问题在于我们不知道转移之后每个水滴的剩余水量,而剩余水量和最多的水都可以通过已蒸发的水量和总共经过的水滴数求出,转换后复杂度降低,符合我们的要求。
于是我们把储存对象改为已蒸发水量,外层枚举一个总共经过的水滴数,定义喝完当前的水后还差 \(k\) 滴水没喝,\(val\) 为排序后的水滴横坐标,可以得到状态转移方程:
\(f_{l,r,0}=\min(f_{l+1,r,1}+k \times |val_r-val_l|,f_{l+1,r,0}+k \times |val_{l+1}-val_l|)\)
\(f_{l,r,1}=\min(f_{l,r-1,1}+k \times |val_r-val_{r-1}|,f_{l,r-1,0}+k \times |val_{l}-val_{r}|)\)
实现时还需要注意特判只喝一个水滴时的边界情况。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll n,m;
ll val[1000001];
ll dp[301][301][2];
ll calc(int lim){
ll ans=0x3f3f3f3f3f3f3f3f;
for(int len=1;len<=lim;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
if(len==1)dp[l][r][0]=dp[l][r][1]=lim*abs(val[l]);
else{
dp[l][r][0]=dp[l][r][1]=0x3f3f3f3f3f3f3f3f;
dp[l][r][0]=min(dp[l][r][0],dp[l+1][r][0]+(lim-len+1)*abs(val[l+1]-val[l]));
dp[l][r][0]=min(dp[l][r][0],dp[l+1][r][1]+(lim-len+1)*abs(val[r]-val[l]));
dp[l][r][1]=min(dp[l][r][1],dp[l][r-1][1]+(lim-len+1)*abs(val[r]-val[r-1]));
dp[l][r][1]=min(dp[l][r][1],dp[l][r-1][0]+(lim-len+1)*abs(val[l]-val[r]));
}
if(len==lim) ans=min(ans,min(dp[l][r][0],dp[l][r][1]));
}
}
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>val[i];
sort(val+1,val+n+1);
ll ans=0;
for(int i=1;i<=n;i++)ans=max(ans,i*m-calc(i));
cout<<ans<<endl;
}