HDU6946. Sequence 动态规划+线段树优化
HDU6946. Sequence
题面:
问题描述:
定义:一个序列的唯一数数量为这个序列中出现且仅出现一次的数字个数。
比如,\(\{1,2,1,2\}\)的唯一数数量为\(0\),因为其中没有出现且仅出现一次的数字;\(\{5,6,7,6,6\}\)的唯一数数量为\(2\),因为\(5\)和\(7\)是这个序列当中出现且仅出现一次的数字。
给定一个长度为\(N\)的序列。你需要将它们分割成\(M\)个部分(每一部分是连续的子序列),使得这\(M\)个部分的唯一数数量之和最大。
输入:
有多组测试数据。
对于每一个测试数据,第一行包括\(2\)个整数\(N\)和\(M\)。
第二行包括\(N\)个整数\(A_1,A_2,\dots,A_N\),表示这个序列。
\(1 \leq M,A_i \leq N\),\(2 \leq M \leq 10\)。保证所有测试数据的\(N\)之和不超过\(2\times10^5\)。
输出:
对于每一个测试数据,在一行内输出一个整数,表示最大的唯一数数量之和。
样例输入:
2 2
1 1
3 2
1 1 1
4 2
1 2 2 1
6 3
1 1 2 2 3 3
样例输出:
2
1
4
4
分析:
考虑动态规划。
考虑前\(i\)个数,分成\(j\)部分的最优解。
我们发现在考虑加进第\(i\)个数的时候,它要么和第\(i-1\)个数处于同一个部分,要么自己成为新的一部分的第\(1\)个数。
对于第二种情况,新的一部分的贡献,直接为\(1\)即可。
对于第一种情况,第\(i\)个数对这一部分产生的贡献需要根据这一部分的数字情况进行考虑。不妨设这一部分的第\(1\)个数在整体的位置为\(k\)。
- 若\(k\)到\(i-1\)位置上没有出现第\(i\)个数,则第\(i\)个数的贡献为\(1\)。
- 若\(k\)到\(i-1\)位置上出现一次第\(i\)个数,则第\(i\)个数的贡献为\(-1\)。
- 若\(k\)到\(i-1\)位置上出现两次及以上次第\(i\)个数,则第\(i\)个数的贡献为\(0\)。
我们不妨这样定义状态,设\(g(i,j,k)\)为考虑前\(i\)个数,分成\(j\)个部分,最后一部分的起始位置为\(k\)的最优解\((j \leq k\leq i)\)。
设此时\(i\)位置的贡献为\(v(k,i)(j \leq k<i)\),则有如下转移方程:
先不谈\(v\)计算的问题,光转移就需要\(O(n^2 m)\)的复杂度,这显然是无法接受的。考虑优化。
我们发现对于第\(i\)个位置的数,随着\(k\)由小变大,贡献是一段为\(0\)(可能没有),一段\(-1\)(可能没有),一段\(1\)(可能没有)这样分布的,分段点也很明显。不妨设从第\(i\)个位置的数往前,第\(1\)次再出现这个数的位置为\(p_1(i)\),第\(2\)次再出现这个数的位置为\(p_2(i)\),则有如下式子:
而且在上面的转移方程中\(i\)只和\(i-1\)相关,可以利用滚动数组的思想去掉\(g\)中的\(i\)。
同时我们可以定义\(f(i,j)=\max\limits_{j \leq x \leq i}\{g(i,j,x)\}\),显然\(f(i,j)\)的意义就是考虑前\(i\)个字符,分成\(j\)个部分的最优解。
上面的转移方程可以变成,
这已经很显然了,我们需要一个支持区间修改,单点修改,维护区间最大值的数据结构,线段树即可胜任。
\(p_1(i)\)和\(p_2(i)\)均可以\(O(n)\)预处理,遍历\(j\)是\(O(m)\)的,遍历\(i\)是\(O(n)\)的,转移的前\(2\)个式子涉及区间修改,是\(O(\log n)\)的,第\(3\)个式子涉及单点修改,是\(O(\log n)\)的,第\(4\)个式子涉及区间查询,是\(O(\log n)\)的,所以单次转移是\(O(\log n)\)的。最终复杂度即为\(O(nm \log n)\)。
代码:
⚠ 由于某些原因,这里的f[maxm][maxn]
第一维是j
,第二维是i
,即\(f(i,j)\)表示为f[j][i]
。
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
#define ls nodes[rt << 1]
#define rs nodes[rt << 1 | 1]
#define fa nodes[rt]
const int maxn = 2e5 + 10;
const int maxm = 15;
int pre1[maxn], pre2[maxn];
int now[maxn];
int a[maxn];
int n, m;
struct Node {
int l, r;
int max_num;
int tag;
};
struct SegTree {
Node nodes[maxn << 2];
void build(int rt, int l, int r) {
fa.l = l, fa.r = r;
fa.max_num = fa.tag = 0;
if (l == r)
return;
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
}
void update_node(int rt, int val) {
fa.max_num += val;
fa.tag += val;
}
void pushup(int rt) { fa.max_num = max(ls.max_num, rs.max_num); }
void pushdown(int rt) {
if (fa.tag) {
update_node(rt << 1, fa.tag);
update_node(rt << 1 | 1, fa.tag);
fa.tag = 0;
}
}
void update(int rt, int L, int R, int val) {
if (L <= fa.l && fa.r <= R) {
update_node(rt, val);
return;
}
pushdown(rt);
if (L <= ls.r)
update(rt << 1, L, R, val);
if (R >= rs.l)
update(rt << 1 | 1, L, R, val);
pushup(rt);
}
int query(int rt, int L, int R) {
if (L <= fa.l && fa.r <= R) {
return fa.max_num;
}
int res = 0;
pushdown(rt);
if (L <= ls.r)
res = max(res, query(rt << 1, L, R));
if (R >= rs.l)
res = max(res, query(rt << 1 | 1, L, R));
return res;
}
} seg;
int f[maxm][maxn];
int main() {
while (scanf("%d%d", &n, &m) == 2) {
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
memset(now, 0, sizeof(int) * (n + 10));
memset(pre1, 0, sizeof(int) * (n + 10));
memset(pre2, 0, sizeof(int) * (n + 10));
for (int i = 1; i <= n; i++) {
pre1[i] = now[a[i]];
pre2[i] = pre1[pre1[i]];
now[a[i]] = i;
}
int res = 0;
// 注意f[1][i]需要单独计算
for (int i = 1; i <= n; i++) {
if (pre2[i]) {
;
} else if (pre1[i]) {
res--;
} else {
res++;
}
f[1][i] = res;
}
for (int j = 2; j <= m; j++) {
seg.build(1, 1, n);
for (int i = j; i <= n; i++) {
// pre1[i]+1, i-1 : +1
// pre2[i]+1, pre1[i]: -1
if (pre1[i] + 1 <= i - 1) {
if (pre1[i] + 1 >= j) {
seg.update(1, pre1[i] + 1, i - 1, 1);
} else if (i - 1 >= j) {
seg.update(1, j, i - 1, 1);
}
}
if (pre2[i] + 1 <= pre1[i]) {
if (pre2[i] + 1 >= j) {
seg.update(1, pre2[i] + 1, pre1[i], -1);
} else if (pre1[i] >= j) {
seg.update(1, j, pre1[i], -1);
}
}
seg.update(1, i, i, f[j - 1][i - 1] + 1);
f[j][i] = seg.query(1, j, i);
}
}
printf("%d\n", f[m][n]);
}
return 0;
}
原文地址:https://www.cnblogs.com/Bamboo-Wind/p/15214165.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法