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;
}

浙公网安备 33010602011771号