传送门: https://codeforces.com/contest/1527/problem/E
当 时 E 题 没 有 时 间 写 , 但 是 20 分 钟 足 够 了 。 当时E题没有时间写,但是20分钟足够了。 当时E题没有时间写,但是20分钟足够了。
题意
将
一
个
长
为
n
的
序
列
,
分
成
k
段
。
将一个长为n的序列,分成k段。
将一个长为n的序列,分成k段。
在
每
段
中
,
对
于
每
个
数
字
来
说
,
贡
献
为
最
后
一
次
出
现
的
位
置
减
第
一
次
出
现
的
位
置
,
即
l
a
s
t
(
x
)
−
f
i
r
s
t
(
x
)
在每段中,对于每个数字来说,贡献为最后一次出现的位置减第一次出现的位置,即last(x)-first(x)
在每段中,对于每个数字来说,贡献为最后一次出现的位置减第一次出现的位置,即last(x)−first(x)
求 分 成 k 段 后 , 最 小 的 总 贡 献 。 求分成k段后,最小的总贡献。 求分成k段后,最小的总贡献。
思路
n 分 成 k 段 , 再 看 看 数 据 范 围 , n ≤ 35000 , k ≤ 100 , d p [ n ] [ k ] n分成k段,再看看数据范围,n\leq 35000,k\leq 100,dp[n][k] n分成k段,再看看数据范围,n≤35000,k≤100,dp[n][k]
d
p
[
n
]
[
k
]
表
示
前
n
个
数
字
分
成
k
段
后
的
最
小
总
贡
献
。
dp[n][k]表示前n个数字分成k段后的最小总贡献。
dp[n][k]表示前n个数字分成k段后的最小总贡献。
那
么
转
移
方
程
就
非
常
好
写
:
那么转移方程就非常好写:
那么转移方程就非常好写:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
k
]
+
v
a
l
[
k
+
1
]
[
j
]
dp[i][j]=dp[i-1][k]+val[k+1][j]
dp[i][j]=dp[i−1][k]+val[k+1][j]
v
a
l
[
i
]
[
j
]
表
示
[
i
,
j
]
内
所
有
数
字
的
贡
献
。
val[i][j]表示[i,j]内所有数字的贡献。
val[i][j]表示[i,j]内所有数字的贡献。
很
容
易
推
出
k
∈
[
i
−
1
,
j
−
1
]
很容易推出k \in [i-1,j-1]
很容易推出k∈[i−1,j−1]
这 样 我 们 的 复 杂 度 为 O ( n 2 k ) , 是 不 行 的 , 不 过 O ( n k ) 是 可 以 的 , 所 以 我 们 想 办 法 消 掉 一 个 n 。 这样我们的复杂度为O(n^2k),是不行的,不过O(nk)是可以的,所以我们想办法消掉一个n。 这样我们的复杂度为O(n2k),是不行的,不过O(nk)是可以的,所以我们想办法消掉一个n。
因
为
我
们
的
d
p
[
i
]
[
j
]
都
是
由
d
p
[
i
−
1
]
[
j
−
1
]
推
出
来
的
。
因为我们的dp[i][j]都是由dp[i-1][j-1]推出来的。
因为我们的dp[i][j]都是由dp[i−1][j−1]推出来的。
而
我
们
的
目
的
就
是
找
到
一
个
k
使
得
d
p
[
i
−
1
]
[
k
]
+
v
a
l
[
k
+
1
]
[
j
]
最
小
,
而
k
∈
[
i
−
1
,
j
−
1
]
.
而我们的目的就是找到一个k使得dp[i-1][k]+val[k+1][j]最小,而k \in [i-1,j-1].
而我们的目的就是找到一个k使得dp[i−1][k]+val[k+1][j]最小,而k∈[i−1,j−1].
进
而
我
们
可
以
用
线
段
树
围
绕
k
维
护
区
间
最
小
值
,
这
样
我
们
可
以
在
l
o
g
内
找
到
这
样
的
k
进
行
转
移
。
进而我们可以用线段树围绕k维护区间最小值,这样我们可以在log内找到这样的k进行转移。
进而我们可以用线段树围绕k维护区间最小值,这样我们可以在log内找到这样的k进行转移。
假 设 我 们 当 前 要 将 第 j 个 数 字 分 类 , 则 [ 1 , 前 一 个 位 置 − 1 ] 的 贡 献 都 要 改 变 , 变 多 少 呢 ? 多 j − ( 前 一 个 位 置 ) 。 假设我们当前要将第j个数字分类,则[1,前一个位置-1]的贡献都要改变,变多少呢?多j-(前一个位置)。 假设我们当前要将第j个数字分类,则[1,前一个位置−1]的贡献都要改变,变多少呢?多j−(前一个位置)。
也 就 是 修 改 : 也就是修改: 也就是修改:
if(from[j] != -1) modify(1, 1, from[j] - 1, j - from[j]);
查 询 是 在 [ i − 1 , j − 1 ] 找 , 也 就 是 : 查询是在[i-1,j-1]找,也就是: 查询是在[i−1,j−1]找,也就是:
dp[j][i] = min(dp[j - 1][i - 1], query(1, i - 1, j - 1));
最 终 时 间 复 杂 度 为 O ( n k log n ) 最终时间复杂度为O(nk\log n) 最终时间复杂度为O(nklogn)
Code
#include "bits/stdc++.h"
using namespace std;
const ll INF = 0x3f3f3f3f;
#define lc u << 1
#define rc u << 1 | 1
#define mid (t[u].l + t[u].r) / 2
const int N = 3e5 + 10;
const int K = 100 + 10;
int dp[N][K];
struct Tree {
int l, r;
int mn;
int tag;
}t[N << 1];
inline void push_up(int u) {
t[u].mn = min(t[lc].mn, t[rc].mn);
}
inline void push_down(int u) {
if(!t[u].tag) return ;
t[lc].tag += t[u].tag;
t[rc].tag += t[u].tag;
t[lc].mn += t[u].tag;
t[rc].mn += t[u].tag;
t[u].tag = 0;
}
void build(int u, int l, int r, int k) {
t[u].l = l; t[u].r = r;
t[u].tag = 0; t[u].mn = INF;
if(l == r) {
t[u].mn = dp[l][k];
return ;
}
int m = (l + r) / 2;
build(lc, l, m, k);
build(rc, m + 1, r, k);
push_up(u);
}
void modify(int u, int ql, int qr, int val) {
if(ql <= t[u].l && t[u].r <= qr) {
t[u].mn += val;
t[u].tag += val;
return ;
}
push_down(u);
if(ql <= mid) modify(lc, ql, qr, val);
if(qr > mid) modify(rc, ql, qr, val);
push_up(u);
}
int query(int u, int ql, int qr) {
if(ql <= t[u].l && t[u].r <= qr) return t[u].mn;
push_down(u);
int ans = INF;
if(ql <= mid) ans = min(ans, query(lc, ql, qr));
if(qr > mid) ans = min(ans, query(rc, ql, qr));
return ans;
}
void solve() {
int n, k; cin >> n >> k;
vector<int> a(n + 1), pre(n + 1, -1), from(n + 1);
for(int i = 1;i <= n; i++) cin >> a[i];
for(int i = 1;i <= n; i++) {
dp[i][1] = dp[i - 1][1];
from[i] = pre[a[i]];
if(from[i] != -1) dp[i][1] += i - from[i];
pre[a[i]] = i;
}
for(int i = 2;i <= k; i++) {
build(1, 1, n, i - 1);
for(int j = i;j <= n; j++) {
if(from[j] != -1) modify(1, 1, from[j] - 1, j - from[j]);
dp[j][i] = min(dp[j - 1][i - 1], query(1, i - 1, j - 1));
// cout << j << " " << "分成" << i << "段:" << " " << dp[j][i] << endl;
}
}
cout << dp[n][k] << endl;
}
signed main() {
solve();
}