P4954 [USACO09OPEN] Tower of Hay G 做题记录

P4954 [USACO09OPEN] Tower of Hay G

Description

给定长度为 \(n\) 的序列 \(A\),将其分成若干段,使其每一段的和单调不升,求最多能划分为多少段。

\(1\leq n\leq 10^5,1\leq A_i\leq 10^4\)

Solution

为了方便,我们先把 \(A\) 翻转。现在我们要让每一段的和单调不降

首先有一个看似很对的贪心:每次贪心地让最后一段刚刚大于等于上一段。

但仔细想想就可以发现问题,比如 5 1 2 4 6 7,上述算法会分出 [5][1 2 4][6 7],但实际上可以分出四段 [5 1][2 4][6][7]

上述算法不正确的根本原因在于我们的目光过于短浅,这一层小了可能导致后面的大。

但我们仍然能够从中获得启发:

若一种方案是所有合法方案中最后一段的和最小的,在此基础上最后两段的和是最小的,以此类推,则这个方案一定是最优方案。

设满足上述条件的方案为 \(P\),再设任意一个不满足上面条件的方案为 \(Q\)

我们从后向前,找到 \(Q\) 中第一个与 \(P\) 不同的段。此时 \(Q\) 的这一段的和一定大于比 \(P\) 这一段的和。

\(Q\) 中这一段的前若干个元素放到前一段,使得 \(P,Q\) 这一段相同。若调整后前面的段不合法,则继续对前面的段进行移动,直到得到一个合法的 \(Q'\)

对于最后得到的 \(Q'\),继续找第一个与 \(P\) 不同的段,重复以上过程,直到 \(Q\) 变为了 \(P\)

可以发现,\(Q\) 的段数一定不会减少,即 \(P\) 一定不劣于 \(Q\)

于是我们设 \(f_i\)\(1\sim i\) 最后一段的和最小是多少,\(g_i\)\(1\sim i\) 最多分几段。

\(s_i\)\(A_1\sim A_i\) 的和。容易得到,\(f_i=s_i-s_j,g_i=g_j+1\)\(j\)\(i\) 之前最大的满足 \(s_i-s_j\geq f_j\),即 \(f_j+s_j\leq s_i\) 的位置。

直接这样转移是 \(O(n^2)\) 的。状态已经不能优化,考虑优化转移。

注意到这个模型与单调队列优化比较像,考虑什么样的 \(x\) 一定不是最优决策。

若有 \(x,y\) 满足 \(x<y\land f_x+s_x>f_y+s_y\),则 \(x\) 一定不优于 \(y\)\(x\) 在后面的位置也一定不会成为最优决策。

于是我们可以维护出 \(f_x+s_x\) 单调不降的单调队列,每次找到这样的 \(j\) 进行转移即可。

时间复杂度为 \(O(n)\)

int n,a[N];
ll s[N],f[N],g[N];
int q[N],l,r;

signed main(){
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	reverse(a+1,a+n+1);
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	q[l=r=1]=0;
	for(int i=1;i<=n;i++){
		int lst=0;
		while(l<r&&f[q[l+1]]+s[q[l+1]]<=s[i]) l++;
		f[i]=s[i]-s[q[l]];
		g[i]=g[q[l]]+1;
		while(l<=r&&f[i]+s[i]<f[q[r]]+s[q[r]]) r--;
		q[++r]=i;
	}
	printf("%d\n",g[n]);
    return 0;
}
posted @ 2025-05-16 22:15  XP3301_Pipi  阅读(19)  评论(0)    收藏  举报
Title