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 }
View Code

 

转载于:https://www.cnblogs.com/ww3113306/p/9886828.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值