【HDU3480】Division-DP+四边形不等式优化+贪心

测试地址:Division

题目大意:在一个有N个元素的整数集合S中选出M个子集,使得这M个子集的并为S,并且使总花费最小。选择一个子集的花费是(该子集中最大的元素-该子集中最小的元素)^2。

做法:首先贪心分析,我们选择的子集在排过序后的数列中肯定是连续的一段,因为如果选择的子集不连续,则一定不比选择连续的更优,而且我们知道这些子集一定不会相交,所以我们就可以将原数列排序后DP。设dp(i,j)为前j个元素分为i个子集的最小花费,w(i,j)为选择从i到j的元素为一个子集的花费,可以得知w(i,j)=(S[j]-S[i])^2,状态转移方程为:dp(i,j)=min(dp(i-1,k-1)+w(k,j)) (i≤k≤j),这是一个O(N^2*M)的方程,直接做肯定无法通过。于是我们考虑优化,我们可以看出这个式子是一个典型的区间型DP的式子,自然而然的联想到用四边形不等式优化。设s(i,j)为使dp(i,j)最小的最大的决策变量k,只要证明出w满足区间包含单调性而且满足四边形不等式,就能推出dp满足四边形不等式,然后就可以推出s单调,然后就能优化了。这里只写出w的证明(不要问我为什么,后面的证明我也不会......):

设i≤i'<j≤j',则:

区间包含单调性:即证明w(i,j')≥w(i',j),这个根据w的定义是显然的。

四边形不等式:即证明w(i,j)+w(i',j')≤w(i,j')+w(i',j)。把式子展开后左边得S[i]^2-2S[i]S[j]+S[j]^2+S[i']^2-2S[i']S[j']+S[j']^2,右边得S[i]^2-2S[i]S[j']+S[j']^2+S[i']^2-2S[i']S[j]+S[j]^2。两边减去S[i]^2+S[j]^2+S[i']^2+S[j']^2,再除以-2,于是我们只需证明S[i]S[j]+S[i']S[j']≥S[i]S[j']+S[i']S[j]即可。用左边减去右边,整理得到(S[i']-S[i])(S[j']-S[j]),由于S是升序的,根据条件得到这个式子≥0,定理得证。

因为s单调,所以s(i-1,j)≤s(i,j)≤s(i,j+1),于是缩小了DP时决策变量的枚举范围,根据一通证明可以知道改进后的方程的复杂度是O(NM),可以通过该题。而且由于本题的状态方程有特殊性,所以空间可以通过滚动数组压缩到O(N)。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 1000000000
using namespace std;
int T,n,m,a[10010];
int dp[2][10010],s[2][10010];

bool cmp(int a,int b)
{
  return a<b;
}

int main()
{
  scanf("%d",&T);
  for(int t=1;t<=T;t++)
  {
    scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1,cmp);
	
	memset(s,0,sizeof(s));
	int past=0,now=1;
	for(int i=1;i<=n;i++) dp[past][i]=inf;
	for(int i=1;i<=n;i++) s[past][i]=1;
	for(int i=1;i<=m;i++)
	{
	  s[now][n+1]=n;
	  for(int j=n;j>=i;j--)
	  {
	    dp[now][j]=inf;
	    for(int k=s[past][j];k<=s[now][j+1];k++)
		  if (dp[now][j]>=dp[past][k-1]+(a[j]-a[k])*(a[j]-a[k]))
		  {
		    dp[now][j]=dp[past][k-1]+(a[j]-a[k])*(a[j]-a[k]);
			s[now][j]=k;
		  }
	  }
	  swap(past,now);
	}
	
	printf("Case %d: %d\n",t,dp[past][n]);
  }
  
  return 0;
}


posted @ 2017-04-18 20:08  Maxwei_wzj  阅读(91)  评论(0编辑  收藏  举报