【题解&&单调队列优化dp】划分

划分

在这里插入图片描述


分析:

首先,我们目光着眼于部分分
我们尝试用 O ( n 3 ) O(n^3) O(n3)的朴素dp去解决这个问题
f [ i ] [ j ] 表示划分到第 i 个位置,且上一个位置是 j 的最小运行时间是多少 f[i][j]表示划分到第i个位置,且上一个位置是j的最小运行时间是多少 f[i][j]表示划分到第i个位置,且上一个位置是j的最小运行时间是多少
然后按照常规思路,我们就枚举上一个端点和上上端点
f [ i ] [ j ] = M i n ( f [ j − 1 ] [ k ] + ( s [ i ] − s [ j − 1 ] ) 2 ) ( 当且仅当 s [ i ] − s [ j − 1 ] > = s [ j − 1 ] − s [ k − 1 ] ) f[i][j]=Min(f[j-1][k]+(s[i]-s[j-1])^2)(当且仅当s[i]-s[j-1]>=s[j-1]-s[k-1]) f[i][j]=Min(f[j1][k]+(s[i]s[j1])2)(当且仅当s[i]s[j1]>=s[j1]s[k1])

但是,我们真的需要枚举每一个端点吗?

我们知道,完全平方有一个美妙的特性: a 2 + b 2 < = ( a + b ) 2 a^2+b^2<=(a+b)^2 a2+b2<=(a+b)2
对于这道题而言,就是说,如果有两段,这两段的存在是合法的,那么就没必要将他们合成一段去处理
换句话说,划分方案一定是段数越多越优。
那么上面的dp,还有必要去枚举每一个端点吗?
显然是不要的,应用那个性质,我们只需要取找离i最近的满足 s [ j − 1 ] − s [ k − 1 ] < = s [ i ] − s [ j − 1 ] s[j-1]-s[k-1]<=s[i]-s[j-1] s[j1]s[k1]<=s[i]s[j1]的那个j和k即可。
枚举完j之后,我们想要找k有两种方法,第一种就是直接二分,第二种就是直接用一个数组 L a [ j ] La[j] La[j]去记录可以转移到j的最近的位置(由于是越近越优,所以转移过来的最近点是唯一的),然后由这个位置直接判断当前转移是否合法即可,( s [ i ] − s [ j − 1 ] > = s [ j − 1 ] − s [ L a [ j − 1 ] ]    ( 1 ) s[i]-s[j-1]>=s[j-1]-s[La[j-1]]\ \ (1) s[i]s[j1]>=s[j1]s[La[j1]]  (1))
前一个的复杂度 O ( n 2 l o g ) O(n^2log) O(n2log),后一个 O ( n 2 ) O(n^2) O(n2)

我们继续观察式(1)
l [ i ] = s [ i ] − s [ L a [ i ] ] l[i]=s[i]-s[La[i]] l[i]=s[i]s[La[i]]
那么上式就可以变成 A ( i ) = s [ i ] > = s [ j − 1 ] + l [ j − 1 ] A(i)=s[i]>=s[j-1]+l[j-1] A(i)=s[i]>=s[j1]+l[j1]
也就是说我们需要找满足上述条件的最大的 j j j
那么我们为什么可以用单调队列呢?
显然, s [ i + 1 ] > = s [ i ] > = s [ j − 1 ] + l [ j − 1 ] s[i+1]>=s[i]>=s[j-1]+l[j-1] s[i+1]>=s[i]>=s[j1]+l[j1]
也就是说,随着i的增大,可选的j的集合是在上一个i的基础上不断变大的(性质1)
且如果 j 1 < j 2 & & A ( j 1 ) < = A ( j 2 ) j1<j2\&\&A(j1)<=A(j2) j1<j2&&A(j1)<=A(j2) j 1 j_1 j1一定优于 j 2 j_2 j2(性质2)
满足以上两个性质之后,我们就可以用单调队列去优化dp了
性质1其实保证了决策点的单调性,当前的决策点可以由上一个决策点的状态继承
而状态2其实就是满足了我们可以用当前优秀的决策点去替换之前更劣的决策点的性质。
这道题想过可能需要int128或者高精度
这里并没有用(不是因为我懒)


#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 4e7+10,P = (1<<30);
int n,op;
int f[N],q[N],l[N];
int he,ta;

int Calc(int x){return x*1ll*x;}
int a[N],s[N];

signed main(){
	cin>>n>>op;
	if (op == 0){
		for (int i = 1; i <= n; i++) cin>>a[i];
	}
	he = 1;
	q[++ta] = 0;
	for (int i = 1; i <= n; i++) s[i] = s[i-1]+a[i];
	f[0] = 0; l[0] =0 ;
	for (int i = 1; i <= n; i++){
		int now = s[i];
		while (he < ta && now >= l[q[he+1]]) ++he;//决策点单调性
		if (he <= ta) if (now >= l[q[he]]){
			f[i] = f[q[he]]+Calc(s[i]-s[q[he]]);
			l[i] = 2*s[i]-s[q[he]];
			while (l[i] < l[q[ta]] && he <= ta) ta--;//优替换劣
			q[++ta] = i;
		}
	}
	cout<<f[n];
	return 0;
}
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值