hdu3507

题意: 给n(n<=10^6)个非负数字,放在一个数组num中,再给一个特殊值m。求将这个数组分成任意多个区间,每个区间[a,b]的值定义为( sigma(num[i] | (a<=i<=b)) ) ^ 2 + m.要区间值总和最小,并输出此最小值  (PS:这道题不用考虑暴int问题,当然这是AC以后才发现的)。

解题思路: 定义sum[i]=sigma(num[j] | (1<=j<=i)) (这里假设num数组下标从1开始)

     定义f(j,i) = (sum[i]-sum[j])^2+m (区间[j+1,i]的值)

     定义dp[i]为从1到i这段的题意要求的那个最小值,根据其定义就有dp[i]=min{ dp[j]+f(j,i) | 0<=j<i } (****)。(思考为什么这样定义?)

     另外定义一个仅作标识用的量dp[j,i],表示从j到i这段题意要求的最小区间值和,即dp[1,k]=dp[k]。

裸的枚举当然是不科学的,效率太低。与上一篇博客略有类似,计算dp[i]要用以前的结果,如何对要枚举的状态进行优化呢?

令(k<j<i) ,x>0;

  1. 若有dp[k]+f(k,i)> dp[j]+f(j,i),那么一定有dp[i+x] > dp[k]+f(k+1,x+i); 也就是说 k一定不是对应着i的一个可能最优解。 在求解dp[i+x]时,k点就不用枚举了。
  2. 若有dp[k]+f(k,i)<= dp[j]+f(j,i),那么一定有dp[i+x] > dp[j]+f(j,i+x)。

重新思考一下(****)dp[i]的定义,应用数学归纳法:

dp[1]=f(0,1) 或者dp[1]=f(0,1)+dp[0] (dp[0]=0)

dp[2]=min{dp[1]+f(2,2),dp[0]+f(1,2)} (刚好对应两种区间拆分方式)

假设dp[k]用上式定义也正确,那么

  dp[k+1]=min{ dp[1,j]+dp[j+1,k+1] | (j <= k) },如果dp[j+1,k+1]==f(j,k+1),原式自然成立;

  那即使不成立呢? 也就是说存在一个j<jj<k, 使得dp[j+1,k+1]==f(j,jj)+dp[jj+1,k+1],所以

   dp[1,j]+f(j,jj)+dp[jj+1,k+1]描述的是什么呢?dp[1,j]+f(j,jj)正是dp[jj]的一个可能最优解。dp[k+1]=min{dp[jj]+dp[jj+1,k+1], dp[k+1] },原式成立!

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <algorithm>
 5 using namespace std;
 6 const int maxn=500100;
 7 int num[maxn],pl;
 8 int sum[maxn];
 9 int dp[maxn];
10 int que[maxn];
11 int f(int j,int i){
12     return (sum[i]-sum[j])*(sum[i]-sum[j])+pl;
13 }
14 int gety(int j,int k){
15     return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];
16 }
17 int getx(int j,int k){
18     return (sum[j]-sum[k])<<1;
19 }
20 int main()
21 {
22     int n;
23     while(scanf("%d%d",&n,&pl) != EOF){
24         for(int i=1;i<=n;i++)
25             scanf("%d",&num[i]);
26         sum[0]=dp[0]=0;
27         for(int i=1;i<=n;i++)
28             sum[i]=sum[i-1]+num[i];
29         int head=0,tail=0;
30         que[tail++]=0;
31         for(int i=1;i<=n;i++){
32             while(head+1 < tail && gety(que[head+1],que[head])<=getx(que[head+1],que[head])*sum[i])
33                 head++;
34             dp[i]=dp[que[head]]+f(que[head],i);
35             while((head+1 < tail) && gety(i,que[tail-2])*getx(que[tail-1],que[tail-2])<=gety(que[tail-1],que[tail-2])*getx(i,que[tail-2]))
36                 tail--;
37             que[tail++]=i;
38         }
39         printf("%d\n",dp[n]);
40     }
41     return 0;
42 }
View Code

 

posted on 2013-08-30 21:03  男神发量  阅读(312)  评论(0编辑  收藏  举报