HT-018 Div3 能量消耗 题解 [ 绿 ] [ 线性 dp ] [ 前缀和优化 ]

能量消耗:一个前缀和优化 dp 的大典题,要是数据水一点 \(O(n^3)\) 都能硬草过去。

思路

显然,定义 \(dp[i]\) 为考虑前 \(i\) 个塔,并且将第 \(i\) 个塔开启,将前面的精灵全部收集的最小代价。

于是转移:

\[dp[i]=min(dp[i],dp[j]+w(j,i)+c[i]) \]

其中 \(0\le j<i \le m\)\(w(j,i)\) 表示收集从塔 \(j\)\(i\) 的所有精灵到塔 \(i\) 所花费的代价。

总体复杂度 \(O(n^3)\)\(1000\) 数据卡的有点紧,我们尝试优化。

前缀和优化

对于 \(w(j,i)\) ,发现它可以由 \(w(j,i-1)\) 和他们之间的精灵计算得来。

于是我们枚举每一个起点 \(j\) ,后面扫一遍所有塔确定 \(i\) ,顺带用一个指针维护。

这题恶心之处就在于这个指针的边界特判。

\(O(n^2)\) 计算完 \(w(j,i)\) 后, dp 复杂度就降为 \(O(n^2)\) 了,可以过。

坑点

因为 \(dp[i]\) 表示的是第 \(i\) 个塔开启时的最小花费,所以可能出现最后一个塔不开启的情况,答案不一定是 \(dp[m]\)

因此,我们统计答案的时候,只需要找满足位置大于等于最后一个精灵的位置的塔的 \(dp\) 值,取一个 \(\min\) 即可。

场上没判这点,怒挂 20pts 。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
ll a[1005],b[1005],c[1005],n,m,w[1005][1005],lst[1005],dp[1005],ans=0x3f3f3f3f3f3f3f3f;
int main()
{
	freopen("energy.in","r",stdin);
	freopen("energy.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	cin>>m;
	for(int i=1;i<=m;i++)cin>>b[i];
	for(int i=1;i<=m;i++)cin>>c[i];
	lst[0]=0;
	for(int i=1;i<=m;i++)
	{
		for(int j=n;j>=1;j--)
		{
			if(a[j]<=b[i])
			{
				lst[i]=j;
				break;
			}
		}
	}
	b[0]=-0x3f3f3f3f;
	for(int i=0;i<m;i++)
	{
		ll now=0,sm=0,p=lst[i]+1,lpos=0;
		for(int j=i+1;j<=m;j++)
		{
			while(p<=lst[j])
			{
				sm+=now*(a[p]-lpos);
				now++;
				lpos=a[p];
				p++;
			}
			sm+=now*(b[j]-lpos);
			lpos=b[j];
			w[i][j]=sm;
		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<i;j++)
		{
			dp[i]=min(dp[i],dp[j]+w[j][i]+c[i]);
		}
	}
	for(int i=1;i<=m;i++)
	{
		if(a[n]<=b[i])ans=min(ans,dp[i]);
	}
	cout<<ans;
	return 0;
}
posted @ 2024-08-01 22:54  KS_Fszha  阅读(51)  评论(0)    收藏  举报