一道单调队列的模板题。
看到数据范围不难想到一维(DP)数组(f_i)表示将(1)到(i)的数合并后满足单调不降的最小合并次数。
考虑(f_i)是由什么转移得到的。发现(f_i)并不只是和(f_{i-1})有关,实际上,由于合并的性质,(f_i)可以由前面任意一个(f_j)得到,但前提是由(i)到(j)合并得到的数要不小于为了得到(f_j)所合并后的序列的最后一个数。
我们设(s_i)表示(p_i)的前缀和,(last_i)表示得到(f_j)所合并后的序列的最后一个数。那么转移方程就是(f_i=min{f_j+i-j-1}(s_i-s_jgeq last_j))。这样暴力转移是(O(n^2))的。
考虑优化转移。对于(k=j+1)来说,(f_kleq f_j+1)。因为如果(f_k>f_j+1)的话,我们只需要在(f_j)的基础上将新加入的数与前面合并即可得到(f_k=f_j+1)。而(j)每增加一,(f_i+i-j-1)一定会减少(1)。也就是说我们要找的(j)其实是满足(s_i-s_jgeq last_j)的最大的(j)。
对式子(s_i-s_jgeq last_j)进行变形得到(s_j+last_jleq s_i)。发现(s_i)是单调上升的,也就是对于一个固定的(j)来说,(s_j+last_j)的值越小,就越有可能在更多的转移中被选择。
对于(j<k),如果(s_k+last_kleq s_j+last_j),也就是(k)在比(j)后入队的同时(即比(j)优),还比(j)更容易在更多的选择中被转移,那么(j)也就没用了,因为无论如何能选择(j)的情况一定可以选择(k)。
int l = 1, r = 0;
for(int i = 1; i <= n; i ++)
{
while(l <= r && s[q[l]] + last[q[l]] <= s[i]) l ++;
f[i] = f[q[l - 1]] + i - q[l - 1] - 1;
last[i] = s[i] - s[q[l - 1]];
while(l <= r && s[i] + last[i] <= s[q[r]] + last[q[r]]) r --;
q[++r] = i;
}