P5336 - 成绩单 题解

一个挺神仙的区间 DP,细想起来又其实是非常平凡的,毕竟每一步撕烤都有迹可循。听说原来好像是个黑题/jy,0rz〇rz


先来探讨一下方案长什么样子。取了一个区间后,以后取的区间要么与该区间无交,要么包含它(真正取的元素使它的补)。所以方案呈一个以包含关系为纽带的树形结构,根是 \([1,n]\),每个区间取的元素为它减去它的若干直接包含的区间。没有相交,这就很适合转化为子问题。

考虑在初始的全局局面 \([1,n]\) 上做出一步决策。看起来比较难:可决策的只有若干直接包含的区间,以及剩下的元素。枚举最后一个直接包含的区间的左右端点的话,其内部可以归约到子问题,但是外部变成了 \([1,n]-I\),变成一个四不像的东西。要按照此思路一直决策下去的话,会得到 \(2^n\) 个状态,封闭不住直接爆炸。于是只能考虑决策后者。

但是后者看上去更难决策,这枚举都没法枚举。但是注意到剩下来的元素的代价至于 min 和 max 有关,于是我们可以直接枚举 min 和 max(以下认为值域大小也是 \(n\),离散化即可)!先考虑确实剩下来至少一个元素的情况,这样转移所需要的信息是 \([1,n]\) 中选掉一些直接包含的区间,使得剩下来元素全部 \(\in[mn,mx]\),最小代价(代价其实就是每个直接包含的区间作为子问题的答案之和)为多少。这看起来就是个挺常规的区间 DP 了。以及直接包含的区间作为子问题的答案需要用到所有区间 \([l,r]\) 答案,此时我们正式设计状态:\(dp_{l,r}\) 表示子问题 \([l,r]\) 的答案,\(f_{l,r,i,j}\) 表示 \([l,r]\) 中只剩 \([i,j]\) 内元素的最小代价。目标 \(dp_{1,n}\),转移的话他们两个互相转移,相辅相成,缺一不可

\(f\) 的转移的话,对 \(r\) 进行决策,分两种情况:若 \(a_r\in[i,j]\),则 \(r\) 可以剩下,\(r\) 往左移一格;\(r\) 不剩下而作为直接包含的区间的元素的话,寻找左端点 \(k\),转移到 \(f_{l,k-1,i,j}+dp_{k,r}\)\(dp\) 的转移,当剩下至少一个元素时,枚举 min 和 max \(i,j\),转移到 \(f_{l,r,i,j}+A+B(i-j)^2\)。当一个元素都不剩的时候,此时转移是容易的,一个复杂的想法是另搞一个 DP 专门表示一个元素都不剩的情况,然后枚举 \(r\) 所在直接包含区间的左端点进行转移,然后再给 \(dp_{l,r}\) chkmin。但其实可以直接在原 \(dp\) 上枚举断点 \(i\),这样虽然会误统计到 \([l,i-1]\) 有元素剩下的情况,但无伤大雅,第一该统计的情况一个都没漏,第二多统计的情况并没有超出所有要统计情况的范围,跟据 max 的可叠加性,这不影响答案。

最后有个小小的环状转移需要考虑一下:\(f_{l,r,i,j}\)​ 直接转移到 \(dp_{l,r}\)​,\(dp_{l,r}\)​ 转移到 \(f_{l,r,i,j}+A+B(i-j)^2\)​。注意到当 \(f_{l,r,i,j}\)​ 直接 chkmin \(dp_{l,r}\)​ 时,情况是一个都不剩,这种情况并不需要被 \(dp_{l,r}\)​ 此时转移时考虑。之所以 \(f\) 要进行这一步更新,是因为保证 \([l,r]\) 的超区间的 \(f\) 值无误。所以我们可以先放弃 \(f\)​ 的这一步更新,把 \(dp\) 搞完之后再将 \(f\) 的这一步补上。

总复杂度 \(\mathrm O\!\left(n^5\right)\)

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=60;
int sq(int x){return x*x;}
int n;
int A,B;
int a[N],b[N];
vector<int> nums;
void discrete(){
	sort(nums.begin(),nums.end());
	nums.resize(unique(nums.begin(),nums.end())-nums.begin());
	for(int i=1;i<=n;i++)b[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin()+1;
}
int f[N][N][N][N];
int dp[N][N],mn[N][N],mx[N][N];
signed main(){
	cin>>n>>A>>B;
	for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
	discrete();
	for(int l=n;l;l--)for(int r=l;r<=n;r++){
		mn[l][r]=inf;mx[l][r]=-inf;
		for(int i=l;i<=r;i++)mn[l][r]=min(mn[l][r],a[i]),mx[l][r]=max(mx[l][r],a[i]);
	}
	for(int l=n;l;l--)for(int r=l;r<=n;r++){
		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++){
			f[l][r][i][j]=inf;
			if(i<=b[r]&&b[r]<=j)f[l][r][i][j]=min(f[l][r][i][j],f[l][r-1][i][j]);
			for(int k=l+1;k<=r;k++)f[l][r][i][j]=min(f[l][r][i][j],f[l][k-1][i][j]+dp[k][r]);
		}
		dp[l][r]=inf;
		for(int i=l;i<=r;i++)dp[l][r]=min(dp[l][r],dp[l][i-1]+A+B*sq(mx[i][r]-mn[i][r]));
		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)dp[l][r]=min(dp[l][r],f[l][r][i][j]+A+B*sq(nums[i-1]-nums[j-1]));
		for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)f[l][r][i][j]=min(f[l][r][i][j],dp[l][r]);
	}
	cout<<dp[1][n];
	return 0;
}
posted @ 2021-10-05 23:29  ycx060617  阅读(45)  评论(0编辑  收藏  举报