洛谷 P5504 柠檬 —— 斜率优化 | 决策dp

题目链接:点我啊╭(╯^╰)╮

题目大意:

     n n n 个数字,每个数字为 s [ i ] s[i] s[i]
    每次选取一串数字,得到 s 0 × t 2 s_0 \times t^2 s0×t2
     s 0 s_0 s0 为这一串数字的任意一个, t t t 为这串数字有 t t t s 0 s_0 s0
    求全部选取完之后的最大值

解题思路:

     d p [ i ] = d p [ j − 1 ] + s [ i ] ∗ c a l ( j , i ) dp[i] = dp[j-1] + s[i] * cal(j, i) dp[i]=dp[j1]+s[i]cal(j,i)
    明显决策点 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j]
     c a l cal cal 用前缀计算,设 a [ i ] a[i] a[i] s i s_i si 这个数字是相同数字里的第几个
    则 d p [ i ] = d p [ j − 1 ] + s [ i ] ∗ ( a [ i ] − a [ j ] + 1 ) 2 dp[i] = dp[j-1] + s[i] * (a[i] - a[j] + 1) ^ 2 dp[i]=dp[j1]+s[i](a[i]a[j]+1)2
    复杂度: O ( n 2 ) O(n^2) O(n2)
    但是这道题的决策是倒过来的!!!因此要在队尾操作!!!


    斜率优化
     d p [ i ] = d p [ j − 1 ] + s i × a i 2 − 2 s i a i a j + s i a j 2 + 2 s i a i − 2 s i a j + s i dp[i] = dp[j-1] + s_i \times a_i ^ 2 - 2s_i a_i a_j + s_i a_j^2 + 2 s_i a_i - 2s_i a_j + s_i dp[i]=dp[j1]+si×ai22siaiaj+siaj2+2siai2siaj+si
    发现有与 i i i j j j 都相关的项,则提取出来,把只和 i i i 有关的项分离出去
     d p [ i ] = − 2 s i a i a j + d p [ j − 1 ] + s i a j 2 − 2 s i a j + . . . dp[i] = - 2 s_i a_i a_j + dp[j-1] + s_i a_j ^ 2 - 2 s_i a_j + ... dp[i]=2siaiaj+dp[j1]+siaj22siaj+...
    设 j < k < i j<k<i j<k<i d p [ i ] [ j ] < = d p [ i ] [ k ] dp[i][j] <= dp[i][k] dp[i][j]<=dp[i][k] a k > a j a_k>a_j ak>aj,得
( d p [ j − 1 ] + s i a j 2 − 2 s i a j ) − ( d p [ k − 1 ] + s i a k 2 − 2 s i a k ) a j − a k ≥ 2 s i a i \frac{(dp[j-1] + s_i a_j ^ 2 - 2 s_i a_j) - (dp[k-1] + s_i a_k ^ 2 - 2 s_i a_k)}{a_j - a_k} ≥ 2 s_i a_i ajak(dp[j1]+siaj22siaj)(dp[k1]+siak22siak)2siai
    符号为 ≥ ≥ ,维护上凸包,斜率递减,由于 2 s i a i 2 s_i a_i 2siai 是递增,所以在队尾操作
    对于一个 s i si si 而言, 2 s i a i 2 s_i a_i 2siai 是单调的,因此不需要二分
    时间复杂度: O ( n ) O(n) O(n)


    决策单调
    发现平方的增长是很快的
    若 i i i 的决策点有 j 1 j_1 j1 j 2 j_2 j2 j 1 < j 2 j_1 < j_2 j1<j2 a [ j 1 ] < a [ j 2 ] a[j_1] < a[j_2] a[j1]<a[j2] d p [ i ] ( j 1 ) > d p [ i ] ( j 2 ) dp[i](j_1) > dp[i](j_2) dp[i](j1)>dp[i](j2)
    那么随着 i i i 的增大, d p [ i ] ( j 1 ) dp[i](j_1) dp[i](j1) 增长的肯定要比 d p [ i ] ( j 2 ) dp[i](j_2) dp[i](j2)
    因此 j 2 j_2 j2 可以直接删掉,满足决策单调
    注意这里的决策单调是倒序单调的
    时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)


斜率优化:

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 1e5 + 5;
int n;
ll s[maxn], a[maxn];
ll num[maxn], dp[maxn];
vector <int> q[maxn];

ll Y(int i){
	return dp[i-1] + s[i] * a[i] * a[i] - 2 * s[i] * a[i];
}
ll X(int i){
	return a[i];
}

double slope(int i, int j){
	return 1.0 * (Y(i) - Y(j)) / (X(i) - X(j));
}

#define sz q[s[i]].size()
#define t1 q[t][sz-1]
#define t2 q[t][sz-2]

ll cal(int i, int j){
	return dp[j-1] + s[i] * (a[i] - a[j] + 1) * (a[i] - a[j] + 1);
}

signed main() {
	scanf("%d", &n);
	for(int i=1; i<=n; i++) {
		scanf("%lld", s+i);
		a[i] = ++num[s[i]];
	}
	for(int i=1; i<=n; i++){
		int t = s[i];
		while(sz>1 && slope(t1, i)>=slope(t2, t1)) q[t].pop_back();
		q[t].push_back(i);
		while(sz>1 && slope(t2, t1)<2*s[i]*a[i]) q[t].pop_back();
//		while(sz>1 && cal(i, t1)<=cal(i, t2)) q[t].pop_back();
		dp[i] = cal(i, t1);
	}
	printf("%lld\n", dp[n]);
}

决策单调:

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 1e5 + 5;
int n;
ll a[maxn], s[maxn];
ll dp[maxn], num[maxn];
vector <int> q[maxn];

#define sz q[t].size()
#define t1 q[t][sz-1]
#define t2 q[t][sz-2]

ll cal(int i, int j){
	return dp[i-1] + 1ll * j * j * s[i];
}

int ck(int i, int j){
	int l = max(a[i], a[j]), r = n, mid;
	while(l <= r){
		mid = l + r >> 1;
		if(cal(i, mid-a[i]+1)>=cal(j, mid-a[j]+1)) r = mid - 1;
		else l = mid + 1;
	}
	return l;
}

signed main() {
	scanf("%d", &n);
	for(int i=1; i<=n; i++){
		scanf("%lld", s+i);
		a[i] = ++num[s[i]];
	}
	for(int i=1; i<=n; i++){
		int t = s[i];
		while(sz>1 && ck(t2, t1)<=ck(t1, i)) q[t].pop_back();
		q[t].push_back(i);
		while(sz>1 && ck(t2, t1)<=a[i]) q[t].pop_back();
		dp[i] = cal(t1, a[i] - a[t1] + 1);
	}
	printf("%lld\n", dp[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值