CF868F Yet Another Minimization Problem 分治决策单调性优化DP
题意:
给定一个序列,你要将其分为k段,总的代价为每段的权值之和,求最小代价。
定义一段序列的权值为$\sum_{i = 1}^{n}{\binom{cnt_{i}}{2}}$,其中$cnt_{i}$表示当前这段序列中数字大小为i的数的个数。
题解:
先考虑暴力DP, f[i][j]表示DP到i位,分为j段的最小代价。
则$f[i][j] = min(f[l - 1][j] + sum[l][i])$,其中sum[l][i]表示区间[l, i]分成一段的代价。
然后可以发现,这是具有决策单调性的,简易证明:
首先设l < j < i.
假设f[i][t]从f[j - 1][t - 1]转移而来,则有$f[j - 1][t - 1] + w[j][i] < f[l - 1][t - 1] + w[l][i]$.
现在考虑i + 1的情况,观察到$\binom{cnt_{i}}{2}$的式子经过化简后恰好可以表示0 ~ n - 1这个等差数列的求和公式,因此w[j][i]可以O(1)的转移到w[j][i + 1],即w[j][i + 1] = w[j][i] + sum[j][i];.sum[j][i]表示这个区间内某个颜色的数量(懒得再打一维了,这个理解一下就好,只是这么表示而已)
所以f[i + 1][t] = min(f[j - 1][t - 1] + w[j][i] + sum[j][i], f[l - 1][t - 1] + w[l][i] + sum[l][i]);//可以发现,由于$l < j$,所以$sum[l][i] >= sum[j][i]$,而式子的另一部分,也就是和f[i][t]相同的部分也是左边小于右边,因此j一定比l优。
因此可以利用决策单调性来优化DP,因为无法O(1)得知w[i][j]的值,因此无法用二分单调栈来进行优化,于是我们考虑分治。
不知道如何用分治优化决策单调性戳:决策单调性优化DP
然后注意到对于这题而言,暴力转移的复杂度还是太高了,于是考虑利用以下之前已有的信息,设当前已经被求出权值的区间为[ll, rr],权值为rnt.
那么每次转移的时候暴力将这个区间转移至当前需要的区间,于是就可以做到重复利用之前已经求出的信息。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 101000 5 #define LL long long 6 #define inf 1e18 7 8 int n, k, now, ll, rr; 9 int s[AC], sum[AC]; 10 LL f[AC][22], rnt; 11 12 inline int read() 13 { 14 int x = 0;char c = getchar(); 15 while(c > '9' || c < '0') c = getchar(); 16 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 17 return x; 18 } 19 20 void pre() 21 { 22 n = read(), k = read(); 23 for(R i = 1; i <= n; i ++) s[i] = read(); 24 } 25 26 void cal(int l, int r) 27 { 28 while(rr < r) rnt += sum[s[++ rr]] ++; 29 while(rr > r) rnt -= -- sum[s[rr --]]; 30 while(ll > l) rnt += sum[s[-- ll]] ++; 31 while(ll < l) rnt -= -- sum[s[ll ++]]; 32 } 33 34 void solve(int l, int r, int kl, int kr)//当前区间[l, r],决策点区间[kl, kr] 35 { 36 if(l > r) return ; 37 int mid = (l + r) >> 1, k = -1, b = min(mid, kr); 38 for(R i = kl; i <= b; i ++)//枚举当前段开头 39 { 40 cal(i, mid); 41 if(rnt + f[i - 1][now - 1] < f[mid][now]) 42 f[mid][now] = rnt + f[i - 1][now - 1], k = i; 43 } 44 solve(l, mid - 1, kl, k), solve(mid + 1, r, k, kr); 45 } 46 47 void work() 48 { 49 for(R i = 1; i <= n; i ++) 50 for(R j = 0; j <= k; j ++) f[i][j] = inf; 51 ll = 1, rr = n; 52 for(R i = 1; i <= n; i ++) rnt += sum[s[i]] ++; 53 for(now = 1; now <= k; now ++) solve(1, n, 1, n);//分层做k次 54 printf("%lld\n", f[n][k]); 55 } 56 57 int main() 58 { 59 freopen("in.in", "r", stdin); 60 pre(); 61 work(); 62 fclose(stdin); 63 return 0; 64 }