Description
给定一个序列 \(a\),要把它分成 \(k\) 个子段。每个子段的费用是其中相同元素的对数。求所有子段的费用之和的最小值。
\(2 \leq n \leq 10^5,2 \leq k \leq \min(n,20),1 \leq a_i \leq n\)
Solution
设 \(f_{i,j}\) 表示 \(1\sim i\) 分为 \(j\) 段的最小费用。
那么 \(f_{i,j}=\min\limits_{1\leq k\leq i}\{f_{k-1,j-1}+w(k,i)\}\)。其中 \(w(l,r)\) 表示区间 \([l,r]\) 这一段的费用。
并且可以知道 \(\forall a<b<c<d,w(a,d)+w(b,c)\geq w(a,c)+w(b,d)\)。证明的话可以对每个不同的数 \(a\) 进行考虑,假设 \(a\sim b\) 这段区间 \(a\) 有 \(x\) 个,\(b\sim c\) 中有 \(y\) 个,\(c\sim d\) 中有 \(z\) 个。然后可知 \(w(a,d)=\frac{(1+x+y+z)(x+y+z)}{2}\),其余同理,之后暴力拆开可以直接证明。
满足四边形不等式后那么就可以在 \(O(n\log n)\) 的时间内解决了。并且由于 \(w(i,j)\) 运算复杂度较高,因此我们考虑分治。设 \([l,r]\) 是决策区间,\([L,R]\) 是更新区间。
在分治的过程中计算 \(w\)。由于每层重新清空重新计算 \(w\) 的复杂度是伪的,具体是因为如果把 \(r\sim MID(MID=(L+R)/2)\) 之间的数扫一遍的话复杂度是错的。
因此我们可以模拟莫队的操作只变更端点,这样复杂度可以保证,这是因为保存值的区间的左端点是随着 \([l,r]\) 变化的,右端点是随着 \([L,R]\) 变化的。每个变化都能保证复杂度是 \(O(n\log n)\)。
Code
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5+5; int n, k, a[N], cnt[N], nl = 1, nr; ll g[2][N], ans; void cdq(int l, int r, int L, int R) { int MID = (L+R)>>1, mid, loc = MID, pl = nl, pr = nr; if (r < MID) loc = r; mid = loc; while (nr < MID) ans += cnt[a[++nr]]++; while (nl > loc) ans += cnt[a[--nl]]++; while (nr > MID) ans -= --cnt[a[nr--]]; while (nl < loc) ans -= --cnt[a[nl++]]; g[1][MID] = g[0][loc-1]+ans; for (int i = loc-1; i >= l; i--) { ans += cnt[a[i]]++; if (g[0][i-1]+ans < g[1][MID]) mid = i, g[1][MID] = g[0][i-1]+ans; } nl = l; if (L < MID) cdq(l, mid, L, MID-1); if (MID < R) cdq(mid, r, MID+1, R); while (nr < pr) ans += cnt[a[++nr]]++; while (nl > pl) ans += cnt[a[--nl]]++; while (nr > pr) ans -= --cnt[a[nr--]]; while (nl < pl) ans -= --cnt[a[nl++]]; } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); memset(g[0], 127/3, sizeof(g[0])); g[0][0] = 0; while (k--) { cdq(1, n, 1, n); swap(g[0], g[1]); } printf("%lld\n", g[0][n]); return 0; }
来源:https://www.cnblogs.com/NaVi-Awson/p/12342008.html