洛谷 P5504 檸檬 —— 斜率優化 | 決策dp

題目鏈接:點我啊╭(╯^╰)╮

題目大意:

    nn 個數字,每個數字爲 s[i]s[i]
    每次選取一串數字,得到 s0×t2s_0 \times t^2
    s0s_0 爲這一串數字的任意一個,tt 爲這串數字有 tts0s_0
    求全部選取完之後的最大值

解題思路:

    dp[i]=dp[j]+s[i]cal(j,i)dp[i] = dp[j] + s[i] * cal(j, i)
    明顯決策點 s[i]=s[j]s[i] = s[j]
    calcal 用前綴計算,設 a[i]a[i]sis_i 這個數字是相同數字裏的第幾個
    則 dp[i]=dp[j]+s[i](a[i]a[j]+1)2dp[i] = dp[j] + s[i] * (a[i] - a[j] + 1) ^ 2
    複雜度:O(n2)O(n^2)
    但是這道題的決策是倒過來的!!!因此要在隊尾操作!!!


    斜率優化
    dp[i]=dp[j1]+si×ai22siaiaj+siaj2+2siai2siaj+sidp[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
    發現有與 iijj 都相關的項,則提取出來,把只和 ii 有關的項分離出去
    dp[i]=2siaiaj+dp[j1]+siaj22siaj+...dp[i] = - 2 s_i a_i a_j + dp[j-1] + s_i a_j ^ 2 - 2 s_i a_j + ...
    設 j<k<ij<k<idp[i][j]<=dp[i][k]dp[i][j] <= dp[i][k]ak>aja_k>a_j,得
(dp[j1]+siaj22siaj)(dp[k1]+siak22siak)ajak2siai\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
    符號爲 ,維護上凸包,斜率遞減,由於 2siai2 s_i a_i 是遞增,所以在隊尾操作
    對於一個 sisi 而言,2siai2 s_i a_i 是單調的,因此不需要二分
    時間複雜度: O(n)O(n)


    決策單調
    發現平方的增長是很快的
    若 ii 的決策點有 j1j_1j2j_2j1<j2j_1 < j_2a[j1]<a[j2]a[j_1] < a[j_2]dp[i](j1)>dp[i](j2)dp[i](j_1) > dp[i](j_2)
    那麼隨着 ii 的增大,dp[i](j1)dp[i](j_1) 增長的肯定要比 dp[i](j2)dp[i](j_2)
    因此 j2j_2 可以直接刪掉,滿足決策單調
    注意這裏的決策單調是倒序單調的
    時間複雜度: 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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章