P5665 [CSP-S2019] 划分
P5665 [CSP-S2019] 划分
2048 年,第三十届 CSP 认证的考场上,作为选手的小明打开了第一题。这个题的样例有 \(n\) 组数据,数据从 \(1 \sim n\) 编号,\(i\) 号数据的规模为 \(a_i\)。
小明对该题设计出了一个暴力程序,对于一组规模为 \(u\) 的数据,该程序的运行时间为 \(u^2\)。然而这个程序运行完一组规模为 \(u\) 的数据之后,它将在任何一组规模小于 \(u\) 的数据上运行错误。样例中的 \(a_i\) 不一定递增,但小明又想在不修改程序的情况下正确运行样例,于是小明决定使用一种非常原始的解决方案:将所有数据划分成若干个数据段,段内数据编号连续,接着将同一段内的数据合并成新数据,其规模等于段内原数据的规模之和,小明将让新数据的规模能够递增。
也就是说,小明需要找到一些分界点 \(1 \leq k_1 \lt k_2 \lt \cdots \lt k_p \lt n\),使得
注意 \(p\) 可以为 \(0\) 且此时 \(k_0 = 0\),也就是小明可以将所有数据合并在一起运行。
小明希望他的程序在正确运行样例情况下,运行时间也能尽量小,也就是最小化
小明觉得这个问题非常有趣,并向你请教:给定 \(n\) 和 \(a_i\),请你求出最优划分方案下,小明的程序的最小运行时间。
Solution
看数据范围要 \(O(n)\)
先贪心,发现分的越多越划算
也就是说,我们想满足最后一段的长度最小,这样就分的最多了,总值也就最小了
设dp[i]为 以i结尾的,最后一段的长度的最小值, ans[i]为以i结尾的答案
不难想到观察转移条件 $$sum[i] - sum[j] >= dp[j]$$ (\(j+1~i\))为新的一段
这时候满足转移方程:$$ans[i] = ans[j] + (sum[i] - sum[j])^{2}$$
答案就是 \(ans[n]\) 了
但枚举j是 \(O(n^{2})\) 的, 需要优化一下
通过移项,我们发现满足 \(sum[i] >= sum[j] + dp[j]\) 即可转移
想要分的足够细,我想让 \(j\) 竟可能的大
又发现 \(sum[j] + dp[j]\) 是和 j 正相关的
于是拿单调队列优化变成 \(O(n)\)
剩下的12分要高精度,就不写了
ThinkTwice
再看一下这题和之前遇到过的单调队列的差别
之前遇到的单调队列,都是越先进队越优,越左边越优,但为了满足约数条件,不得不舍弃很美好的head
这题呢? 变成后面进队的越优了,也就是我想想方设法地让head前进
也就是说,单调队列优化dp,其最优解可能在两个方向, 这就需要遵照题意了
但抓住他的本质就不会出问题: 利用和维护单调性, 减少枚举次数
又拓宽眼界啦
Code(88pnts)
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<climits>
#define LL long long
#define REP(i, x, y) for(LL i = (x);i <= (y);i++)
using namespace std;
LL RD(){
LL out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const LL maxn = 40000010;
LL num, type;
LL a[maxn], sum[maxn], dp[maxn], ans[maxn];//dp[i]表示以i结尾的最后一段的最小值
LL pre[maxn];
void init(){
num = RD(), type = RD();
if(type == 0)
REP(i, 1, num)
a[i] = RD(), sum[i] = sum[i - 1] + a[i];
else{
puts("GG");
exit(0);
}
}
LL Q[maxn];
LL head, tail;
void work(){
REP(i, 1, num){
while(head < tail && dp[Q[head + 1]] + sum[Q[head + 1]] <= sum[i])head++;
dp[i] = sum[i] - sum[Q[head]];
ans[i] = ans[Q[head]] + dp[i] * dp[i];
while(head < tail && dp[Q[tail]] + sum[Q[tail]] >= dp[i] + sum[i])tail--;
Q[++tail] = i;
}
cout<<ans[num]<<endl;
}
int main(){
init();
work();
return 0;
}