【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;
}


posted @ 2016-09-16 17:33  Maxwei_wzj  阅读(102)  评论(0编辑  收藏  举报