把n个数分成k个连续序列。对于每个序列,定义花费为:每段值相同的数对个数。求最小花费。
很容易列出二维DP方程:dp[i][j]=min(dp[i-1][k]+cost(k+1,j) k<j dp[i][j]表示前 j 个数字分了 i 个序列。
观察发现,每多分一个序列,当我们向右移动多出来的这个序列的右界时,左界只能单调地向右移动而不能向左,否则将破坏最优性。也就是说,左界具有DP的决策单调性。
那么,用类似于cdq分治的方法,对于每层定义四个值l,r,L,R,分别表示新分的序列的右界范围和左界范围。定义m=(l+r)/2,在每层穷举对于右界为m的序列的左界最优决策是哪里,之后继续递归求解(l,m-1),(m+1,r)两段的解。每多分一个序列复杂度为O(nlogn),总的复杂度是O(knlogn)。
#include <cstdio>
#include <iostream>
#include <string.h>
#include <string>
#include <map>
#include <queue>
#include <deque>
#include <vector>
#include <set>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <stack>
#include <iomanip>
#define mem0(a) memset(a,0,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
using namespace std;
typedef long long ll;
typedef long double ld;
typedef double db;
const int maxn = 100005, inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f3f;
const ld pi = acos(-1.0L);
ll a[maxn], dp[maxn][2], t[maxn];
void solve(int l, int r, int L, int R, int u, ll sum) {
if (l > r) return;
int m = (l + r) / 2, M = -1, i;
ll mc = llinf;
int p = min(m, R);
for (i = l; i <= m; i++) sum += t[a[i]]++;
for (i = L; i <= p; i++) {
sum -= --t[a[i]];
if (sum + dp[i][u ^ 1] < dp[m][u]) {
M = i; dp[m][u] = sum + dp[i][u ^ 1];
}
}
for (i = l; i <= m; i++) sum -= --t[a[i]];
for (i = L; i <= p; i++) sum += t[a[i]]++;
solve(l, m - 1, L, M, u, sum);
for (i = L; i < M; i++) sum -= --t[a[i]];
for (i = l; i <= m; i++) sum += t[a[i]]++;
solve(m + 1, r, M, R, u, sum);
for (i = L; i < M; i++) t[a[i]]++;
for (i = l; i <= m; i++) t[a[i]]--;
}
int main() {
int n, k, i, j;
scanf("%d%d", &n, &k);
mem0(t);
dp[0][0] = 0;
for (i = 1; i <= n; i++) {
scanf("%I64d", &a[i]);
dp[i][0] = dp[i-1][0] + t[a[i]]++;
}
for (i = 1; i < k; i++) {
mem0(t);
for (j = 1; j <= n; j++) dp[j][i % 2] = llinf;
solve(1, n, 1, n, i%2, 0);
}
printf("%I64d\n", dp[n][(k + 1) % 2]);
// system("pause");
return 0;
}