UESTC_摩天轮 2015 UESTC Training for Dynamic Programming<Problem K>

K - 摩天轮

Time Limit: 10000/4000MS (Java/Others)     Memory Limit: 262143/262143KB (Java/Others)
 

一天,冬马被春希和雪菜拉着去一起去游乐园玩。

经过了各种过山车的洗礼后,三人决定去坐摩天轮休息下。

这是一个巨大的摩天轮,每一个车厢能坐任意多的人。现在,等着坐摩天轮的有n个人(包含他们3人),摩天轮还有m个车厢可以坐人。每个人都有自己肥胖程度,出于某些原因,胖子和瘦子坐在同一节车厢就会产生一定的矛盾,这个矛盾的值为(MAXMIN)2,其中MAX为当前车厢里面最胖的人的肥胖程度,MIN为最廋的那个人的肥胖程度。

爱管闲事的春希当然不希望就因为这点小事而使大家的这趟旅途不愉快,于是他决定帮大家安排怎么坐才能使总的矛盾值最小,希望你能帮他找到这个最小的矛盾值

Input

第一行为两个整数nm,分别表示人数和车厢数。(3n10000,1m5000)

第二行为n个整数,wi表示第i个人的肥胖程度。(0wi1000000)

Output

每组数据,输出一个整数,为矛盾的最小值。(答案保证小于231

Sample input and output

Sample InputSample Output
4 2
4 7 10 1
18

解题思路:

这是一道斜率优化DP的题目.

 我们令 f ( i , j ) 表示将前 i 个人分成 j 份所需的最小费用.

F ( i , j ) = min{ F ( u , j -1) + (sum[i] – sum[ u + 1])^2 }

但是这样做的复杂度高达O(N^2*M),对于本题的规模来讲无法承受,我们考虑

K1 < k2 < i ,且 k2 比 k1更优

有式子

 F ( k2 , j - 1 ) + sum( k2 ) ^ 2 -  F( k1 , j -1 ) – sum(k1+1) ^ 2

 ————————————————————— —————————————   < 2 * sum(i)

             sum ( k2 ) – sum( k1+1)

显然这是一个斜率的式子.

假设现在 K(k1 – k2) < 2*sum[i] ,那么k1这个点还有价值吗?

我们注意到sum[i]是单调不减的,也就是说,对于之后的i2,i3…..

K(k1 – k2) < 2*sum[i_x] ,也就是说,k1 完全不可能成为某个点的最优解了,是可以舍弃的.

那么如果现在有 K(k1 – k2) >= 2*sum[i]呢,目前显然是选择k1比k2好,我们应不应该舍弃k2呢?答案是不应该,因为随着i的增大,sum[i]是不减的(也就是说,sum[i]可能会增大),此时 K(k1 – k2) < 2*sum[i] 是可能成立的.

综上所述,我们维护一个单调队列即可,只不过这个单调队列剔除 / 加入元素的条件都是和与斜率有关,这样我们可以在O(1)的时间内得到某个点的最优决策点,复杂度成功降到了O(n*m).

代码使用了滚动数组进行优化

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e4 + 50;
int n,m,h[maxn],f[2][maxn],cur=0,q[maxn];


double slope(int x,int y)
{
   if (h[x+1] == h[y+1])
    return 1e233;
   return (double)(f[cur^1][x] + h[x+1]*h[x+1] - (f[cur^1][y] + h[y+1]*h[y+1])) / (double)(h[x+1] - h[y+1]);
}


int main(int argc,char *argv[])
{
  scanf("%d%d",&n,&m);
  for(int i = 1 ; i <= n ; ++ i) scanf("%d",&h[i]);sort(h+1,h+1+n);
  for(int i = 1;  i <= n ; ++ i) f[cur][i] = (h[i]-h[1])*(h[i]-h[1]);
  for(int i = 2 ; i <= m ; ++ i)
   {
         cur ^= 1;
         int front = 0 , rear = 0 ;
         q[rear++] = 1;
         for(int j = 2 ; j <= n ; ++ j)
          {
                while (rear - front > 1 && slope(q[front],q[front+1]) <= 2*h[j])
                 front++;
                f[cur][j] = f[cur^1][q[front]] + (h[j] - h[q[front]+1])*(h[j] - h[q[front]+1]);
                while(rear - front > 1 && slope(q[rear-2],q[rear-1]) >= slope(q[rear-1],j))
                 rear--;
                q[rear++] = j;
       }
   }
  printf("%d
",f[cur][n]);
  return 0;
}
原文地址:https://www.cnblogs.com/Xiper/p/4539638.html