洛谷 P3648 [APIO2014]序列分割(斜率优化dp)

洛谷 P3648 [APIO2014]序列分割(斜率优化dp)

题目链接:P3648 [APIO2014]序列分割
题解:

这题中我们需要使用二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前考虑前 i i i个点,分割了 j j j次,且最后一次分割位置为 i i i的最大得分,因为数据 n ≤ n \leq n 100000, k ≤ k \leq k 200,因此 n ∗ k ≤ 1 e 7 n*k \leq 1e7 nk1e7,此时我们就需要使用滚动数组来优化内存。
首先使用 p r e [ i ] pre[i] pre[i]表示前 i i i个数的和,我们可以很容易就推出状态转移方程
d p [ i ] [ j ] = m a x ( d p [ j ] [ j − 1 ] + ( p r e [ i ] − p r e [ j ] ) ∗ ( p r e [ n ] − p r e [ i ] ) ) , j ≤ i dp[i][j]=max(dp[j][j-1]+(pre[i]-pre[j])*(pre[n]-pre[i])),j\leq i dp[i][j]=max(dp[j][j1]+(pre[i]pre[j])(pre[n]pre[i])),ji
如果做过我上一篇博客的题,那么很明显我们可以发现这个状态转移方程可以进行斜率优化,考虑当前为第 t t t次分割且 j ≤ k ≤ i j\leq k \leq i jki, k k k j j j优的时满足:
d p [ j ] [ t − 1 ] + ( p r e [ i ] − p r e [ j ] ) ∗ ( p r e [ n ] − p r e [ i ] ) ≤ d p [ k ] [ t − 1 ] + ( p r e [ i ] − p r e [ k ] ) ∗ ( p r e [ n ] − p r e [ i ] ) dp[j][t-1]+(pre[i]-pre[j])*(pre[n]-pre[i])\leq dp[k][t-1]+(pre[i]-pre[k])*(pre[n]-pre[i]) dp[j][t1]+(pre[i]pre[j])(pre[n]pre[i])dp[k][t1]+(pre[i]pre[k])(pre[n]pre[i])
化简后为:
p r e [ n ] − p r e [ i ] ≤ ( d p [ k ] [ t − 1 ] − d p [ j ] [ t − 1 ] ) / ( p r e [ k ] − p r e [ j ] ) pre[n]-pre[i]\leq (dp[k][t-1]-dp[j][t-1])/(pre[k]-pre[j]) pre[n]pre[i](dp[k][t1]dp[j][t1])/(pre[k]pre[j])
使用单调队列维护凸包即可。(注意,因为求斜率时可能 p r e [ j ] = p r e [ k ] pre[j]=pre[k] pre[j]=pre[k],所以要特判一下,直接返回 − 1 e 18 -1e18 1e18

具体细节见代码和注释:
ll dp[N][4];//滚动数组dp[i][j]表示前i个点分割j次的,且最后一次在位置j的得分
ll a[N],pre[N];
int q[N];
long double get_k(int t,int j,int k){
	if(pre[k]==pre[j]){
		return -inf;
	}
	return 1.0*(dp[k][t%3]-dp[j][t%3])/(pre[k]-pre[j]);
}
vector<int>way[210];//存路径
int main(){
/*cout<<setiosflags(ios::fixed)<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(不含整数部分)*/
/*cout<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(含整数部分)*/
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//同步流
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pre[i]=pre[i-1]+a[i];
	}
	for(int t=1;t<=k;t++){//枚举分割次数
		int l=1,r=1;
		q[r]=0;
		for(int i=1;i<=n;i++){
			while(l<r&&get_k(t-1,q[l],q[l+1])>=(pre[n]-pre[i])){
				l++;
			}
			dp[i][t%3]=dp[q[l]][(t-1+3)%3]+(pre[i]-pre[q[l]])*(pre[n]-pre[i]);
			way[t].emplace_back(q[l]);
			while(l<r&&get_k(t-1,q[r-1],q[r])<=get_k(t-1,q[r],i)){
				r--;
			}
			q[++r]=i;
		}
	}
	int ans=1;
	for(int i=1;i<=n;i++){
		if(dp[i][k%3]>dp[ans][k%3]){
			ans=i;
		}
	}
	cout<<dp[ans][k%3]<<endl;
	for(int i=k;i>1;i--){
		cout<<ans<<" ";
		ans=way[i][ans-1];
	}
	cout<<ans<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值