NFLSOJ
T2
CF1076G Array Game
阿瑞遊戲。
考慮從後往前推。如果一個位置後面能走到的位置中有一個必敗態,則一定會走過去,則當前位為必勝態。否則兩人必然在當前位反覆磨,直到一方不得不滾蛋為止。不得不滾蛋的那位就輸了。因此當前位是否必勝只與當前位的奇偶性與後 \(m\) 位的狀態有關。由於 \(m\) 很小,考慮直接壓成一個數,對每一位記錄後面 \(m\) 位的狀態,轉移相當於根據這些狀態推出當前狀態,然後把當前狀態也扔到狀態裡,然後轉移到(序列的)前一位。注意到這實際上是一個只與奇偶性有關的狀態的對映,而且定義域很小。我們直接拿陣列記下這個對映,放到線段樹上維護。區間加法考慮若加的是偶數,則相當於沒變。否則相當於把區間中所有數奇偶性取反。由於每個葉子的對映只有兩種,而且一旦取反就是對整個區間取反,所以我們對每個點都記兩種對映,要取反就把兩個對映交換。最後合併的時候注意對映合併的方向。
程式碼
#include <iostream>
#define int long long
using namespace std;
int n, m, q;
int a[200005];
struct f {
short a[33];
short& operator[](int x) { return a[x]; }
} T[800005][2];
f A[2];
f operator*(f a, f b) {
f c;
for (int i = 0; i < (1 << m); i++) c[i] = b[a[i]];
return c;
}
struct Segment_Tree {
bool tg[1000005];
void tag(int o) {
tg[o] ^= 1;
swap(T[o][0], T[o][1]);
}
void pushdown(int o) {
if (!tg[o])
return;
tag(o << 1);
tag(o << 1 | 1);
tg[o] = 0;
}
void Build(int o, int l, int r) {
if (l == r) {
T[o][0] = A[a[l] & 1];
T[o][1] = A[!(a[r] & 1)];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
T[o][0] = T[o << 1 | 1][0] * T[o << 1][0];
T[o][1] = T[o << 1 | 1][1] * T[o << 1][1];
}
void Change(int o, int l, int r, int L, int R) {
if (L <= l && r <= R) {
tag(o);
return;
}
pushdown(o);
int mid = (l + r) >> 1;
if (L <= mid)
Change(o << 1, l, mid, L, R);
if (R > mid)
Change(o << 1 | 1, mid + 1, r, L, R);
T[o][0] = T[o << 1 | 1][0] * T[o << 1][0];
T[o][1] = T[o << 1 | 1][1] * T[o << 1][1];
}
f Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return T[o][0];
pushdown(o);
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return Query(o << 1 | 1, mid + 1, r, L, R) * Query(o << 1, l, mid, L, R);
}
} seg;
signed main() {
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (short i = 0; i < (1 << m); i++) {
if (i != (1 << m) - 1)
A[0][i] = A[1][i] = (i << 1 | 1);
else {
A[0][i] = (i << 1 | 1);
A[1][i] = (i << 1);
}
if (i & (1 << (m - 1)))
A[0][i] ^= (1 << m), A[1][i] ^= (1 << m);
}
seg.Build(1, 1, n);
while (q--) {
int op, l, r, d;
cin >> op;
if (op == 1) {
cin >> l >> r >> d;
if (d & 1)
seg.Change(1, 1, n, l, r);
} else {
cin >> l >> r;
f tmp = seg.Query(1, 1, n, l, r);
cout << 2 - (tmp[(1 << m) - 1] & 1) << "\n";
}
}
return 0;
}
T3
CF1681E Labyrinth Adventures
考慮在層之間做轉移。注意到一個層的有效位置只有兩個,也就是兩個門的位置。層之間的轉移都是線性的,可以直接使用矩陣刻畫。詢問的時候區間矩陣乘起來,再加上兩頭走到門的距離即可。
程式碼
#include <iostream>
#define int long long
using namespace std;
const int inf = 1000000000;
int n, m;
struct node {
int x, y;
} d[100005][2];
struct Matrix {
int a[2][2];
int* operator[](int x) { return a[x]; }
} T[400005], I, A[400005];
Matrix operator*(Matrix a, Matrix b) {
Matrix c;
c[0][0] = min(a[0][0] + b[0][0], a[0][1] + b[1][0]);
c[0][1] = min(a[0][0] + b[0][1], a[0][1] + b[1][1]);
c[1][0] = min(a[1][0] + b[0][0], a[1][1] + b[1][0]);
c[1][1] = min(a[1][0] + b[0][1], a[1][1] + b[1][1]);
return c;
}
struct Segment_Tree {
void Build(int o, int l, int r) {
if (l == r) {
T[o] = A[l];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
T[o] = T[o << 1] * T[o << 1 | 1];
}
Matrix Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return T[o];
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return Query(o << 1, l, mid, L, R) * Query(o << 1 | 1, mid + 1, r, L, R);
}
} seg;
signed main() {
cin >> n;
for (int i = 1; i < n; i++) cin >> d[i][0].y >> d[i][0].x >> d[i][1].y >> d[i][1].x;
for (int i = 1; i < n - 1; i++) {
A[i][0][0] = (abs(d[i + 1][0].x - d[i][0].x));
A[i][1][1] = (abs(d[i + 1][1].y - d[i][1].y));
A[i][0][1] = (abs(d[i + 1][1].x - d[i][0].x)) + abs(d[i][0].y + 1 - d[i + 1][1].y);
A[i][1][0] = (abs(d[i + 1][0].y - d[i][1].y)) + abs(d[i][1].x + 1 - d[i + 1][0].x);
}
if (n != 2)
seg.Build(1, 1, n - 2);
cin >> m;
while (m--) {
int x1, y1, x2, y2;
cin >> y1 >> x1 >> y2 >> x2;
int ca = max(x1, y1);
int cb = max(x2, y2);
if (ca > cb)
swap(x1, x2), swap(y1, y2), swap(ca, cb);
if (ca != cb) {
--cb;
int a = abs(x1 - d[ca][0].x) + abs(y1 - d[ca][0].y);
int b = abs(x1 - d[ca][1].x) + abs(y1 - d[ca][1].y);
int c = abs(x2 - d[cb][0].x) + abs(y2 - d[cb][0].y - 1) + 1;
int e = abs(x2 - d[cb][1].x - 1) + abs(y2 - d[cb][1].y) + 1;
I[0][0] = a, I[0][1] = b;
I[1][0] = I[1][1] = inf;
if (ca <= cb - 1)
I = I * seg.Query(1, 1, n - 2, ca, cb - 1);
cout << min(I[0][0] + c, I[0][1] + e) + cb - ca << "\n";
} else
cout << abs(x1 - x2) + abs(y1 - y2) << "\n";
}
return 0;
}
T4
CF1609E William the Obvilious
從前往後考慮,我們只關注當前匹配到 \(\texttt{abc}\) 的哪一位,因此把這個東西扔到狀態裡。轉移都是線性的,矩乘刻畫一下即可。
程式碼
#include <iostream>
#define int long long
using namespace std;
const int inf = 1000000000;
int n, q;
string str;
struct Matrix {
int a[3][3];
int* operator[](int x) { return a[x]; }
} M[3], T[400005], I;
Matrix operator*(Matrix a, Matrix b) {
Matrix c;
c[0][0] = min(a[0][0] + b[0][0], min(a[0][1] + b[1][0], a[0][2] + b[2][0]));
c[0][1] = min(a[0][0] + b[0][1], min(a[0][1] + b[1][1], a[0][2] + b[2][1]));
c[0][2] = min(a[0][0] + b[0][2], min(a[0][1] + b[1][2], a[0][2] + b[2][2]));
c[1][0] = min(a[1][0] + b[0][0], min(a[1][1] + b[1][0], a[1][2] + b[2][0]));
c[1][1] = min(a[1][0] + b[0][1], min(a[1][1] + b[1][1], a[1][2] + b[2][1]));
c[1][2] = min(a[1][0] + b[0][2], min(a[1][1] + b[1][2], a[1][2] + b[2][2]));
c[2][0] = min(a[2][0] + b[0][0], min(a[2][1] + b[1][0], a[2][2] + b[2][0]));
c[2][1] = min(a[2][0] + b[0][1], min(a[2][1] + b[1][1], a[2][2] + b[2][1]));
c[2][2] = min(a[2][0] + b[0][2], min(a[2][1] + b[1][2], a[2][2] + b[2][2]));
return c;
}
struct Segment_Tree {
void Build(int o, int l, int r) {
if (l == r) {
T[o] = M[str[l] - 'a'];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
T[o] = T[o << 1] * T[o << 1 | 1];
}
void Change(int o, int l, int r, int x) {
if (l == r) {
T[o] = M[str[x] - 'a'];
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
Change(o << 1, l, mid, x);
else
Change(o << 1 | 1, mid + 1, r, x);
T[o] = T[o << 1] * T[o << 1 | 1];
}
} seg;
signed main() {
cin >> n >> q;
cin >> str;
str = ' ' + str;
M[0][0][0] = 1, M[0][0][1] = 0, M[0][0][2] = inf;
M[0][1][0] = inf, M[0][1][1] = 0, M[0][1][2] = 1;
M[0][2][0] = inf, M[0][2][1] = inf, M[0][2][2] = 0;
M[1][0][0] = 0, M[1][0][1] = 1, M[1][0][2] = inf;
M[1][1][0] = inf, M[1][1][1] = 1, M[1][1][2] = 0;
M[1][2][0] = inf, M[1][2][1] = inf, M[1][2][2] = 0;
M[2][0][0] = 0, M[2][0][1] = 1, M[2][0][2] = inf;
M[2][1][0] = inf, M[2][1][1] = 0, M[2][1][2] = 1;
M[2][2][0] = inf, M[2][2][1] = inf, M[2][2][2] = 1;
seg.Build(1, 1, n);
while (q--) {
int x;
char y;
cin >> x >> y;
str[x] = y;
seg.Change(1, 1, n, x);
I[0][0] = 0;
I[0][1] = I[0][2] = inf;
I[1][0] = I[1][1] = I[1][2] = inf;
I[2][0] = I[2][1] = I[2][2] = inf;
I = I * T[1];
cout << min(I[0][0], min(I[0][1], I[0][2])) << "\n";
}
return 0;
}
T5
CF750E New Year and Old Subsequence
從前往後考慮,我們只關注當前匹配到 \(\texttt{2017}\) 和 \(\texttt{2016}\) 的哪一位。把這個東西扔到狀態裡,注意當匹配到 \(1\) 及其後的位時要對於 \(6\) 特殊轉移,防止出現 \(\texttt{2016}\)。
程式碼
#include <iostream>
#define int long long
using namespace std;
const int inf = 2147483647;
int n, q;
string str;
struct Matrix {
int a[5][5];
int* operator[](int x) { return a[x]; }
} T[800005], A[10], I, E;
Matrix operator*(Matrix a, Matrix b) {
Matrix c;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
c[i][j] = inf;
for (int k = 0; k < 5; k++)
c[i][j] = min(c[i][j], a[i][k] + b[k][j]);
}
}
return c;
}
struct Segment_Tree {
void Build(int o, int l, int r) {
if (l == r) {
T[o] = A[str[l] - '0'];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
T[o] = T[o << 1] * T[o << 1 | 1];
}
Matrix Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return T[o];
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return Query(o << 1, l, mid, L, R) * Query(o << 1 | 1, mid + 1, r, L, R);
}
} seg;
signed main() {
cin >> n >> q;
cin >> str;
str = ' ' + str;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++)
E[i][j] = (i == j ? 0 : inf);
}
for (int i = 0; i < 10; i++) {
A[i] = E;
if (i != 0 && i != 1 && i != 2 && i != 6 && i != 7)
continue;
else if (i == 2)
A[i][0][0] = 1, A[i][0][1] = 0;
else if (i == 0)
A[i][1][1] = 1, A[i][1][2] = 0;
else if (i == 1)
A[i][2][2] = 1, A[i][2][3] = 0;
else if (i == 7)
A[i][3][3] = 1, A[i][3][4] = 0;
else
A[i][3][3] = 1, A[i][4][4] = 1;
}
seg.Build(1, 1, n);
while (q--) {
int l, r;
cin >> l >> r;
I[0][0] = 0, I[0][1] = inf, I[0][2] = inf, I[0][3] = inf, I[0][4] = inf;
I[1][0] = inf, I[1][1] = inf, I[1][2] = inf, I[1][3] = inf, I[1][4] = inf;
I[2][0] = inf, I[2][1] = inf, I[2][2] = inf, I[2][3] = inf, I[2][4] = inf;
I[3][0] = inf, I[3][1] = inf, I[3][2] = inf, I[3][3] = inf, I[3][4] = inf;
I[4][0] = inf, I[4][1] = inf, I[4][2] = inf, I[4][3] = inf, I[4][4] = inf;
I = I * seg.Query(1, 1, n, l, r);
cout << (I[0][4] >= inf ? -1 : I[0][4]) << "\n";
}
return 0;
}
T6
CF1192B Dynamic Diameter
考慮動態 dp。先把邊權下放到點權,設 \(f[i][0 / 1]\) 表示 \(i\) 子樹內(包括 \(i\) 向上的邊)的直徑和還能向上延伸的最長鏈。重剖一下,將重兒子以外的 dp 值和當前點權視為常數,這些東西對重兒子 dp 值轉移到當前點 dp 值的貢獻可以使用矩陣刻畫。這個矩陣可以根據轉移方程列一下,應當是好列的。接下來考慮修改。首先對於一個點,如果知道其所有輕兒子的 dp 值,則可以根據當前點權構造出當前點的轉移矩陣。也就是修改一個點實際上是修改了它的矩陣。這個矩陣更改後會直接影響到其所在鏈頂的 dp 值,所以這個鏈頂的 dp 值必須重新計算。由於鏈頂是輕兒子,而輕兒子的 dp 值變動會導致其父親的轉移矩陣變化,所以還需要重新計算鏈頂父親的轉移矩陣。然後依次類推,直到更新到根為止。重新計算某個點 dp 值的方法很簡單,由於鏈底必為葉子,直接用這個葉子的初始矩陣按順序乘上鍊上所有點的轉移矩陣即可。線段樹維護轉移矩陣,需要注意矩陣乘法的方向。
程式碼
#include <iostream>
#include <set>
#define int long long
using namespace std;
const int inf = 4000000000000000000;
int n, q, W;
int head[100005], nxt[200005], to[200005], ew[200005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int down[100005], _dfn[100005], son[100005], top[100005], dfn[100005], dep[100005], sz[100005], fa[100005], w[100005], ncnt;
int eu[100005], ev[100005];
int f[100005][2];
multiset<int, greater<int> > st[100005][2];
struct Matrix {
int a[3][3];
int* operator[](int x) { return a[x]; }
Matrix() { a[0][0] = a[0][1] = a[0][2] = a[1][0] = a[1][1] = a[1][2] = a[2][0] = a[2][1] = a[2][2] = -inf; }
} A[100005], T[400005], E;
Matrix operator*(Matrix a, Matrix b) {
Matrix c;
c[0][0] = max(a[0][0] + b[0][0], max(a[0][1] + b[1][0], a[0][2] + b[2][0]));
c[0][1] = max(a[0][0] + b[0][1], max(a[0][1] + b[1][1], a[0][2] + b[2][1]));
c[0][2] = max(a[0][0] + b[0][2], max(a[0][1] + b[1][2], a[0][2] + b[2][2]));
c[1][0] = max(a[1][0] + b[0][0], max(a[1][1] + b[1][0], a[1][2] + b[2][0]));
c[1][1] = max(a[1][0] + b[0][1], max(a[1][1] + b[1][1], a[1][2] + b[2][1]));
c[1][2] = max(a[1][0] + b[0][2], max(a[1][1] + b[1][2], a[1][2] + b[2][2]));
c[2][0] = max(a[2][0] + b[0][0], max(a[2][1] + b[1][0], a[2][2] + b[2][0]));
c[2][1] = max(a[2][0] + b[0][1], max(a[2][1] + b[1][1], a[2][2] + b[2][1]));
c[2][2] = max(a[2][0] + b[0][2], max(a[2][1] + b[1][2], a[2][2] + b[2][2]));
return c;
}
Matrix Construct(int x) {
if (!son[x])
return E;
int g0, g1 = *st[x][1].begin();
if (sz[son[x]] + 1 == sz[x])
g0 = *st[x][0].begin();
else {
int tmp = *st[x][1].begin();
st[x][1].erase(st[x][1].begin());
g0 = max(*st[x][0].begin(), tmp + *st[x][1].begin());
st[x][1].insert(tmp);
}
g1 += w[x];
Matrix ret;
ret[0][0] = ret[2][2] = 0;
ret[1][0] = g1 - w[x];
ret[2][1] = g1;
ret[2][0] = g0;
ret[1][1] = w[x];
return ret;
}
void dfs1(int x, int fa, int d) {
dep[x] = d;
::fa[x] = fa;
sz[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa) {
w[v] = ew[i];
dfs1(v, x, d + 1);
sz[x] += sz[v];
if (sz[v] > sz[son[x]])
son[x] = v;
}
}
}
void dfs2(int x, int t) {
top[x] = t;
_dfn[dfn[x] = ++ncnt] = x;
if (!son[x]) {
down[t] = x;
return;
}
dfs2(son[x], t);
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa[x] && v != son[x])
dfs2(v, v);
}
}
void dfs3(int x) {
f[x][1] = w[x];
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa[x]) {
dfs3(v);
f[x][0] = max(f[x][0], max(f[v][0], f[x][1] - w[x] + f[v][1]));
f[x][1] = max(f[x][1], f[v][1] + w[x]);
if (v != son[x]) {
st[x][0].insert(f[v][0]);
st[x][1].insert(f[v][1]);
}
}
}
if (son[x])
A[dfn[x]] = Construct(x);
else
A[dfn[x]] = E;
}
struct Segment_Tree {
void Build(int o, int l, int r) {
if (l == r) {
T[o] = A[l];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
T[o] = T[o << 1 | 1] * T[o << 1];
}
void Change(int o, int l, int r, int x) {
if (l == r) {
T[o] = A[l];
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
Change(o << 1, l, mid, x);
else
Change(o << 1 | 1, mid + 1, r, x);
T[o] = T[o << 1 | 1] * T[o << 1];
}
Matrix Query(int o, int l, int r, int L, int R) {
if (L > R)
return E;
if (L <= l && r <= R)
return T[o];
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return Query(o << 1 | 1, mid + 1, r, L, R) * Query(o << 1, l, mid, L, R);
}
} seg;
void Change(int x) {
if (son[x]) {
A[dfn[x]] = Construct(x);
seg.Change(1, 1, n, dfn[x]);
}
Matrix I;
while (1) {
int tmp = down[top[x]];
I[0][0] = I[0][2] = 0, I[0][1] = w[tmp];
I = I * seg.Query(1, 1, n, dfn[top[x]], dfn[tmp] - 1);
x = top[x];
int tmp0 = f[x][0], tmp1 = f[x][1];
f[x][0] = I[0][0], f[x][1] = I[0][1];
if (x == 1)
break;
st[fa[x]][0].erase(st[fa[x]][0].find(tmp0));
st[fa[x]][1].erase(st[fa[x]][1].find(tmp1));
st[fa[x]][0].insert(f[x][0]);
st[fa[x]][1].insert(f[x][1]);
x = fa[x];
A[dfn[x]] = Construct(x);
seg.Change(1, 1, n, dfn[x]);
}
}
signed main() {
E[0][0] = E[1][1] = E[2][2] = 0;
cin >> n >> q >> W;
for (int i = 1; i < n; i++) {
int ww;
int &u = eu[i], &v = ev[i];
cin >> u >> v >> ww;
add(u, v, ww);
add(v, u, ww);
}
dfs1(1, 0, 1);
dfs2(1, 1);
dfs3(1);
seg.Build(1, 1, n);
int lans = 0;
while (q--) {
int d, ww;
cin >> d >> ww;
d = (d + lans) % (n - 1) + 1;
ww = (ww + lans) % W;
if (dep[eu[d]] < dep[ev[d]]) {
w[ev[d]] = ww;
Change(ev[d]);
} else {
w[eu[d]] = ww;
Change(eu[d]);
}
cout << (lans = max(f[1][1], f[1][0])) << "\n";
}
return 0;
}
T10
Atcoder ABC228H Histogram
先排序,考慮最終的序列一定是一段一段,每一段都與最後一個數相同,而這最後一個數一直不變。因此直接 dp,發現可以斜率最佳化,然後就做完了。
程式碼
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
pair<int, int> a[200005];
int S[2][200005];
int n, X;
struct Line {
int k, b;
int operator()(int x) { return k * x + b; }
} stk[200005];
int sz;
bool chk(Line a, Line b, Line c) { return (b.b - a.b) * (b.k - c.k) >= (c.b - b.b) * (a.k - b.k); }
int dp[200005];
signed main() {
cin >> n >> X;
for (int i = 1; i <= n; i++) cin >> a[i].first >> a[i].second;
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) S[0][i] = S[0][i - 1] + a[i].second, S[1][i] = S[1][i - 1] + a[i].first * a[i].second;
stk[++sz] = (Line) { 0, 0 };
for (int i = 1, j = 1; i <= n; i++) {
int x = a[i].first;
while (j < sz && stk[j](x) >= stk[j + 1](x)) ++j;
dp[i] = stk[j](x) - S[1][i] + a[i].first * S[0][i] + X;
Line tmp = (Line) { -S[0][i], S[1][i] + dp[i] };
while (sz > j && chk(stk[sz - 1], stk[sz], tmp)) --sz;
stk[++sz] = tmp;
}
cout << dp[n] << "\n";
return 0;
}
···
</details>
### T11
CF1527E Partition Game
直接線段樹最佳化 dp 即可。也可以根據決策單調性分治。
<details>
<summary> 程式碼 </summary>
```cpp
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
int n, k;
int f[35005][105];
int ap[35005];
int a[35005];
struct Segment_Tree {
int mx[140005], tg[140005];
void tag(int o, int v) { tg[o] += v, mx[o] += v; }
void pushdown(int o) {
if (!tg[o])
return;
tag(o << 1, tg[o]);
tag(o << 1 | 1, tg[o]);
tg[o] = 0;
}
void Build(int o, int l, int r, int k) {
tg[o] = 0;
if (l == r) {
mx[o] = f[l][k];
return;
}
int mid = (l + r) >> 1;
Build(o << 1, l, mid, k);
Build(o << 1 | 1, mid + 1, r, k);
mx[o] = min(mx[o << 1], mx[o << 1 | 1]);
}
void Add(int o, int l, int r, int L, int R, int v) {
if (L <= l && r <= R) {
tag(o, v);
return;
}
pushdown(o);
int mid = (l + r) >> 1;
if (L <= mid)
Add(o << 1, l, mid, L, R, v);
if (R > mid)
Add(o << 1 | 1, mid + 1, r, L, R, v);
mx[o] = min(mx[o << 1], mx[o << 1 | 1]);
}
int Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return mx[o];
pushdown(o);
int mid = (l + r) >> 1;
if (R <= mid)
return Query(o << 1, l, mid, L, R);
if (L > mid)
return Query(o << 1 | 1, mid + 1, r, L, R);
return min(Query(o << 1, l, mid, L, R), Query(o << 1 | 1, mid + 1, r, L, R));
}
} seg;
signed main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (ap[a[i]])
f[i][1] = f[i - 1][1] + i - ap[a[i]];
else
f[i][1] = f[i - 1][1];
ap[a[i]] = i;
}
seg.Build(1, 1, n, 1);
for (int x = 2; x <= k; x++) {
memset(ap, 0, sizeof ap);
f[1][x] = 0;
ap[a[1]] = 1;
for (int i = 2; i <= n; i++) {
if (ap[a[i]] > 1)
seg.Add(1, 1, n, 1, ap[a[i]] - 1, i - ap[a[i]]);
ap[a[i]] = i;
f[i][x] = seg.Query(1, 1, n, 1, i - 1);
}
seg.Build(1, 1, n, x);
}
cout << f[n][k] << "\n";
return 0;
}