【HDU4261】Estimation-DP+优先队列优化
测试地址:Estimation
题目大意:将一个长度为n的数列A分成k个连续的段,每一段给一个B值(即如果A[i]和A[j]属于同一段,则B[i]=B[j]),使得Σ|A[i]-B[i]|最小,输出这个最小值。
做法:容易想到DP做法。设f[i][j]为数列前i个数分成j个段的最小的Σ|A[i]-B[i]|,则易得出状态转移方程:
f[i][j]=min(f[r][j-1],sum[r+1][i]) (0≤r<i)
其中sum[a][b]表示区间[a,b]分成一段的情况下这个区间中最小的Σ|A[i]-B[i]|。
这个DP的时间复杂度是O(n^2*k)的,而裸求sum的时间复杂度太高,需要预处理。
我们知道,对于一个段来说,B值取它们的中位数一定最优。维护两个优先队列lower和upper,分别为比中位数小的部分和比中位数大的部分,lower是值越大优先级越大,upper是值越小优先级越大,用s表示upper中元素之和与lower中元素之和的差。维护lower的大小总是(r-l+1)/2,upper的大小总是(r-l+1)-(lower大小)(l,r为所求区间的左右端点坐标),这样的话,如果区间内元素个数是奇数,则upper中最小值就是区间的中位数,此时sum[l][r]=s-upper中最小值;如果区间内元素个数是偶数,则中位数可以为upper中最小值和lower中最大值之间的任何数,此时sum[l][r]=s。
那么,怎么使lower和upper的大小是我们想要的值呢?实际上很简单,如果lower的大小大于我们想要的值,则将lower中的最大值取出放到upper中,如果还大就继续这样操作。反之,如果upper的大小大于我们想要的值,则将upper中的最小值取出放到lower中,如果还大就继续这样操作。由于lower和upper的定义,所以实现这样的操作是很方便的。
以下是本人代码(在hdu上无法通过编译,难道是禁用了priority_queue?求高人指点迷津):
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#define inf 1000000000
using namespace std;
int n,k,a[2005],sum[2005][2005],f[2005][30]={0};
priority_queue<int>lower;
priority_queue<int,vector<int>,greater<int> >upper;
void calc_sum()
{
for(int i=1;i<=n;i++)
{
while(!lower.empty()) lower.pop();
while(!upper.empty()) upper.pop();
int s=0;
for(int j=i;j<=n;j++)
{
if (lower.empty()||a[j]<=lower.top()) {lower.push(a[j]);s-=a[j];}
else {upper.push(a[j]);s+=a[j];}
unsigned int low=(j-i+1)/2,upp=(j-i+1)-low;
if (lower.size()>low)
{
upper.push(lower.top());
s+=2*lower.top();
lower.pop();
}
if (upper.size()>upp)
{
lower.push(upper.top());
s-=2*upper.top();
upper.pop();
}
while(lower.size()&&upper.size()&&lower.top()>upper.top())
{
int u=lower.top(),v=upper.top();
lower.pop();upper.pop();
lower.push(v);upper.push(u);
s=s+2*u-2*v;
}
int ans=s;
if (upp>low) ans-=upper.top();
sum[i][j]=ans;
}
}
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF&&n+k)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
calc_sum();
for(int i=1;i<=n;i++)
for(int j=0;j<=k;j++)
f[i][j]=inf;
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
for(int r=0;r<i;r++)
f[i][j]=min(f[i][j],f[r][j-1]+sum[r+1][i]);
printf("%d\n",f[n][k]);
}
return 0;
}