[CSP2019] 划分 解题报告

[CSP2019] 划分

题意

有一个长度为 \(n\) 的序列 \(a_i\) \((n \le 4 \times 10^7)\), 求

\[(\sum_{i=1}^{k_1} a_i)^2 + (\sum_{i=k_1+1}^{k_2} a_i)^2 \dots (\sum_{i=k_{j-1}+1}^{n} a_i)^2 \]

的最小值.

思路

结论: 上述式子能取到最小值, 当且仅当最后一块 (即\(\sum_{i=k_{j-1}+1}^{n} a_i\)) 取到了最小值.
证明: 暂时不会.

那我们就 \(dp\) 保证最后一块最小就行了.

\(val[i]\) 为以 \(i\) 结尾的序列的最后一块的最小值.

那么对于每一位 \(i\) 只需从 \(i-1\)\(0\) 找到第一位 \(j\) 满足 \(sum[i] - sum[j] \ge val[j]\), \(val[i]\) 即为 \(sum[i]-sum[j]\). (\(sum\) 为前缀和数组)

可以发现, 每次 \(j\) 值是有单调性的, 所以可以考虑单调队列优化.

队头的筛选很简单, 满足要求就 +1, 找到最后一个满足条件的 \(j\) 即可.

再考虑队尾的处理, 如果只是单纯的把当前的 \(i\) 加入队列中的话, 就会出现 "队列中后面满足但前面不满足的情况",
比如说下面这个例子.

7 0
15 19 14 1 5 7 17

\(lst[i]\)\(i\) 的上一个块的终点.

在上面这个例子中, \(lst[6]=2\), 那么 \(i==7\) 时, 队头 \(t1\) 就会在 \(2\) 的位置,
但是, 由于 \(lst[3]=1,\ val[3]=33\), 而 \(sum[7]-sum[3]==30 < val[3]\), 所以 \(t1\) 会停在 \(3\).

但是, \(lst[5]=2\), 所以 \(lst[7]\) 实际上是可以取到 \(5\) 的.

所以, 我们可以发现, 要在加入一个新的选项时, 要去除一些不优情况.

首先想到的是把队列中 \(val[que[t2]] > val[i]\) 的去掉, ( \(t2\) 为队尾 )
但是这样可能会导致后面的一些 \(i\) 取不到满足要求的 \(j\),

所以我们还要保证当 \(que[t2]\) 对于后面的某个数满足要求时, \(i\) 也一定满足要求.

\(t\) 为后面的一个点, 那么就要满足: \(sum[t]-sum[que[t2]] \ge val[que[t2]]\) 可以推导到 \(sum[t]-sum[i] \ge val[i]\).
即 $ val[que[t2]] + sum[que[t2]] \ge val[i] + sum[i] $,

那么, 在把 \(i\) 加入队尾的时候判断上述条件, 若满足则 \(t2--\).

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+7;
int n,ty,que[N],t1=1,t2,pre[N];
ll a[N],sum[N],val[N],ans;
int main(){
  cin>>n>>ty;
  for(int i=1;i<=n;i++){
    scanf("%lld",&a[i]);
    sum[i]=sum[i-1]+a[i];
  }
  que[++t2]=0;
  for(int i=1;i<=n;i++){
    while(t1<t2&&sum[i]-sum[que[t1+1]]>=val[que[t1+1]]) t1++;
    pre[i]=que[t1];
    val[i]=sum[i]-sum[que[t1]];
    while(t2>=t1&&val[i]+sum[i]<=val[que[t2]]+sum[que[t2]]) t2--;
    que[++t2]=i;
  }
  int p=n;
  while(p){
    ans+=val[p]*val[p];
    p=pre[p];
  }
  printf("%lld\n",ans);
  return 0;
}
posted @ 2019-12-08 17:25  BruceW  阅读(162)  评论(0编辑  收藏  举报