P2943 [USACO09MAR]Cleaning Up G
知识点:DP,结论
简述
原题面:Luogu
给定一长度为 \(n\) 的数列 \(a\),给定参数 \(m\),满足 \(1\le a_i\le m\)。
要求将其划分为任意段,每段的代价为该段内权值种类数的平方。
最小化代价之和。
\(1\le a_i\le m\le n\le 4\times 10^4\)。
1S,128MB。
分析
40 pts
显然 DP,设 \(f_{i}\) 表示划分 \(1\sim i\) 的最小代价之和。
每个数都分一段代价最大,初始化 \(\forall 1\le i\le n,\ f_{i} = i\),转移时枚举最后一段,则有:
答案即 \(f_n\)。
预处理区间权值种类数,总复杂度 \(O(n^2)\) 级别。
100 pts
由于分段的最大代价为 \(n\),则不可能存在一段的权值种类数 \(\sqrt n\)。
则上述转移方程中 \(\left| \bigcup_{k=j +1}^{i} \{a_i\}\right|\ge \sqrt n\) 的转移都是无用的,可以考虑仅记录有贡献的转移前驱。
考虑在枚举 \(i\) 的同时维护 \(\operatorname{pos}_{j}\),记录满足 \([\operatorname{pos}_j,i]\) 中有 \(j\) 种权值的最左侧的位置,转移方程变为:
转移的时间复杂度为 \(O(n\sqrt n)\)。
再考虑如何维护 \(\operatorname{pos}\),考虑维护 \(\sqrt n\) 个计数数组 \(\operatorname{cnt}\),第 \(j\) 个计数数组维护有 \(j\) 种权值的区间 \([\operatorname{pos}, i]\) 内各种的权值的个数。
每次更新 \(i\) 后,考虑将 \(a_i\) 加入所有计数数组后产生的影响。对于 \(\operatorname{cnt}_j\),讨论一下:
- 当 \(a_i\) 在 \(\operatorname{cnt}_j\) 中出现过,不会影响权值种类数。
- 当 \(a_i\) 未出现过,且区间权值种类数 \(\ge j\),则令 \(\operatorname{pos}_j\) 不断右移,直至 \(\operatorname{cnt}_j\) 中出现新的一种权值的数量为零,以保证权值种类数为 \(j\)。
\(\operatorname{pos}\) 仅会右移 \(n\) 次,显然上述维护过程的总时空复杂度均为 \(O(n\sqrt n)\)。
程序总时空复杂度均为 \(O(n\sqrt n)\)。
实现
//知识点:贪心,DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 4e4 + 10;
//=============================================================
int n, m, sqrtn, a[kN], f[kN];
int pos[kN], num[210], cnt[210][kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Update(int id_, int val_) {
cnt[id_][val_] ++;
if (cnt[id_][val_] == 1) {
++ num[id_];
while (num[id_] > id_) {
cnt[id_][a[pos[id_]]] --;
num[id_] -= !cnt[id_][a[pos[id_]]];
++ pos[id_];
}
}
}
//=============================================================
int main() {
n = read(), m = read(), sqrtn = sqrt(n);
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = 1; i <= sqrtn; ++ i) pos[i] = 1;
for (int i = 1; i <= n; ++ i) {
f[i] = i;
for (int j = 1; j <= sqrtn; ++ j) {
Update(j, a[i]);
Chkmin(f[i], f[pos[j] - 1] + j * j);
}
}
printf("%d\n", f[n]);
return 0;
}