SPOJ SP16809 EST - Estimation 题解

Description

给定一个长度为 \(n\) 的数组 \(a\) 和一个整数 \(k\),你需要创建另一个长度为 \(n\) 的数组 \(b\)\(b\) 数组需要由 \(k\) 个段组成,并且每个段中的数全部相等。求 \(\sum\limits_{i=1}^{n}|a_i-b_i|\) 的最小值。

其中 \(n\le2000,k\le25\) 并且 \(k\le n\)

Solution

首先考虑朴素的动态规划。设 \(f_{i,k}\) 表示 \(b\) 数组的前 \(i\) 个数分成 \(k\) 段能取到的最小值。显然有

\[f_{i,k}=\min\limits_{j=0}^{i-1}(f_{j,k-1}+cost(j+1,i)) \]

其中 \(cost(j+1,i)= \min(\sum\limits_{l=j+1}^{i}|a_l-b_l|)\)

考虑如何求 \(cost(j+1,i)\)。注意到从 \(b_{j+1}\)\(b_i\) 是属于同一段的,因此都等于一个定值 \(x\)。于是求 \(cost(j+1,i)\) 等价于找一个 \(x\),使 \(\sum\limits_{l=j+1}^{i}|a_l-x|\) 最小。将所有 \(a_l\) 看成数轴上的点,则问题变成在数轴上找一个点,使这个点到一些给定的点的距离之和最小。容易证明这个点取在 \(a_{j+1},a_{j+2}\dots a_{i}\) 的中位数处是最优的。

这个结论画个图就很好理解了。假设 \(i-(j+1)+1\) 是偶数,当 \(x\) 取中位数时,如下如所示:

那么 \(\sum\limits_{l=j+1}^{i}|a_l-x|=|a_{j+4}-a_{j+3}|+|a_{j+5}-a_{j+2}|+|a_i-a_{j+1}|\)。如果将 \(x\) 的位置移动到 \(a_{j+4}\)\(a_{j+5}\) 之间,如下图所示:

那么结果就比原来多了 \(|x-a_{j+4}|\) 这一部分,显然不是最优的。将 \(x\) 移到其它区间同理,只有当 \(x\)\([a_{j+3},a_{j+4}]\) 这一区间时才是最优的,取中位数即可。

\(i-(j+1)+1\) 是奇数时的证明也同理。

可以先将 \(a_{j+1},a_{j+2}\dots a_i\) 存到一个新数组中并排序得到中位数 \(a_{mid}\),再扫描一遍统计 \(\sum\limits_{l=j+1}^{i}|a_l-a_{mid}|\),就可以在 \(n \log n\) 的时间内求出 \(cost(j+1,i)\) 了。

这样一来,我们就得到了一个 \(O(n^3k\log n)\) 的优秀做法,可以拿下 TLE。

接下来考虑如何优化。注意到 \(cost(j+1,i)\) 可能被用了很多次,因此可以预处理。设 \(g_{i,j}=cost(i+1,j)\)。但是预处理的复杂度仍然是 \(O(n^3\log n)\) 的,无法承受。复杂度瓶颈在于排序。

为了更快地求出 \(a_{i+1}\)\(a_j\) 的中位数,我们可以使用“对顶堆”,建立一个大根堆和一个小根堆。设 \(cnt=j-(i+1)+1\),大根堆存前 \(\lfloor\frac{cnt}{2}\rfloor\) 小的数,小根堆存剩下的数。从 \(a_{i+1}\) 开始扫描,每次将当前扫描到的数 \(x\) 和大根堆堆顶元素比较,如果 \(x\) 比较小就将 \(x\) 插入大根堆,否则插入小根堆。每次插入后对两个堆进行维护,确保大根堆的大小为 \(\lfloor\frac{cnt}{2}\rfloor\)。这样,大根堆的堆顶元素就是 \(i+1\)\((i+1)+cnt-1\) 的中位数了。设 \(a_{mid}\) 是当前 \(cnt\) 个数的中位数,想要求出 \(\sum\limits_{l=i+1}^{(i+1)+cnt-1}|a_l-a_{mid}|\),只需在扫描的时候同时维护大根堆和小根堆中每个元素的和即可。

每次插入操作的复杂度为 \(O(\log n)\),我们成功让预处理的时间复杂度降到了 \(O(n^2\log n)\)。总时间复杂度 \(O(n^2(k+\log n))\),本题时间限制为 6s,已经足够。

Code

#include<bits/stdc++.h>
using namespace std;
const int INF=0x7fffffff;
const int N=2010,M=30;
int a[N],f[N][M],g[N][N],b[N];
int n,m;
void init()
{
    for(int i=1;i<=n;i++)
    {
        priority_queue<int> q1;//大根堆
        priority_queue<int,vector<int>,greater<int>> q2;//小根堆
        q1.push(-INF),q2.push(INF);
        int sum1=0,sum2=0;
        for(int j=i;j<=n;j++)
        {
            //插入
            if(q1.empty()||a[j]<=q1.top()) q1.push(a[j]),sum1+=a[j];
            else q2.push(a[j]),sum2+=a[j];
            //维护
            if(q1.size()>q2.size()+1)
            {
                int x=q1.top();
                q2.push(x);
                q1.pop();
                sum1-=x,sum2+=x;
            }
            else if(q1.size()<q2.size()) 
            {
                int x=q2.top();
                q1.push(x);
                q2.pop();
                sum2-=x,sum1+=x;
            }
            //求出g[i][j]
            int x=q1.top();
            g[i][j]=q1.size()*x-sum1+sum2-q2.size()*x;
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF&&(n||m))
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        init();
        memset(f,0x3f,sizeof(f));
        f[0][0]=0;
        for(int i=1;i<=n;i++)
            for(int k=1;k<=m;k++)
                for(int j=0;j<i;j++)
                    f[i][k]=min(f[i][k],f[j][k-1]+g[j+1][i]);
        printf("%d\n",f[n][m]);
    }
    return 0;
}
posted @   __Star_Sky  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示