Luogu P2943 [USACO09MAR]Cleaning Up G

题目描述

https://www.luogu.com.cn/problem/CF1093F

简要题意:给定一个长为 $n$ 的序列 $a$,需要将 $a$ 划分成若干段,使得所有段的代价和最小,一段的代价定义为这一段中不同的数字的种数的平方

$n\le 40000$

Solution

容易发现 $dp$ 的转移并不满足决策单调性,所以我们要从别的方向出发

注意到答案的上界为 $n$,那么一个长度为 $len$ 的区间的不同数字的种数不能超过 $\sqrt len$,否则一定不优

另外还能发现 $f$ 一定单调不降

那么我们可以考虑记录以 $i$ 为终点,区间有 $k$ 种不同数字的最靠左的端点

$k$ 只需要记录到 $\sqrt n$ 即可,且随着 $i$ 的增加可以均摊 $O(1)$ 转移

所以总的时间复杂度就是 $O(n\sqrt n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <cmath>
#define maxn 40010
#define maxs 210
#define INF 1000000000
using namespace std;

int n, m, a[maxn];

int p[maxs], cnt[maxs][maxn], sum[maxs];

int f[maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> m; int N = sqrt(n);
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= N; ++i) p[i] = 0;
fill(f, f + maxn, INF); f[0] = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= N; ++j) {
if (++cnt[j][a[i]] == 1) ++sum[j];
while (sum[j] > j) {
if (--cnt[j][a[p[j] + 1]] == 0) --sum[j];
++p[j];
}
f[i] = min(f[i], f[p[j]] + sum[j] * sum[j]);
}
cout << f[n] << "\n";
return 0;
}