• 【HNOI2008】玩具装箱


    P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1...N的N件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=j-i+Σ(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小

    第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7
    输出最小费用

    提取信息:
    1.n件玩具
    2.可以将某段玩具压缩,需要付出代价
    3.压缩次数不限
    4.要求计算最小费用
    我们首先设计状态:
    直观的:f[i]表示到了第i件玩具
    这时我们发现剩余的2个信息无需也无法在向状态空间中添加,因为压缩次数是不限的,或者说,这样就足够描述状态空间了。
    所以我们就用f[i]表示处理到第i件玩具时的最小费用
    接着思考转移:
    我们能够用来连接转移的信息只有将某段玩具压缩了,所以我们很容易地想到:
    设j表示i之前的某个点,我们要压缩j+1到i之间的玩具。
    我们再记一个前缀和数组S表示数组C的前缀和
    这样我们就能写出状态转移方程
    f[i] = min{f[j] + (i - j + S[i] - S[j] - L - 1) ^ 2}
    复杂度O(n^2)
    肯定无法通过这道题,思考如何优化,首先我们将循环变量i看做一个常量
    将min函数去掉,得到
    f[i] = f[j] + (i - j + S[i] - S[j] - L - 1)^2
    为了方便,我们将多项式分离成: i + S[i]和 j + S[j] + L + 1
    用a[i]存储i+S[i],即a[i] = i + S[i],同理:b[i] = i + S[i] + L + 1
    这样我们将方程变为
    f[i] = f[j] + (a[i] - b[j])^2
    ==> f[i] = f[j] + a[i]^2 - 2 * a[i] * b[j] + b[j]^2
    ==> f[j] + b[j]^2 = 2 * a[i] * b[j] + f[i] - a[i]^2
    我们将b[j]看做横坐标,2*a[i]看做斜率,f[j] + b[j]^2看做y
    这个式子就可以看做以2*a[i]为斜率的直线,纵截距为f[i] - a[i]^2,且截距未知
    于是我们很容易能想到,我们要在某个位置取到最优决策,就要求截距最小化
    我们很容易看出2*a[i]和f[i] - a[i]^2是常量,而对于自变量b[j]本身具有单调性,所以显然该方程具有决策单调性,所以我们使用斜率优化
    对于任意三个决策点(f[j1] + b[j1]^2, b[j]), (f[j2] + b[j2]^2, b[j2]),(f[j3] + b[j3]^2, b[j3]), 设b[j1] < b[j2] < b[j3],若j2为最优决策,则有
    f[j1] + b[j1]^2 - b[j1] > f[j2] + b[j2]^2 - b[j2]
    同理:f[j2] + b[j2]^2 - b[j2] < f[j3] + b[j3]^2 - b[j3]
    可以变形为:f[1] + b[j1]^2 - f[j2] - b[j2]^2 / (b[j1] - b[j2]) > 1
    f[j2] + b[j2] ^ 2 - f[j3] - b[j3]^2 / (b[j2] - b[j3]) < 1
    也就是说,加入某个中间点j2是最优决策,则必须满足:
    f[j1] + b[j1]^2 - f[j2] - b[j2]^2 / (b[j1] - b[j2]) > f[j2] + b[j2] ^ 2 - f[j3] - b[j3]^2 / (b[j2] - b[j3])
    //2*a[i]被约分掉了
    然后我们可以发现等式两边实际分别表示的是直线j1j2和直线j2j3的斜率
    也就是说我们可以通过维护斜率的单调递增来保证每次我们取到的是最优解,也就是维护一个下凸壳
    对于下凸壳的维护,我们需要建立一个支持如下操作的单调队列:
    1.每次取出队头q[l], q[l + 1],若斜率 (f[q[l]] + b[q[l]]^2 - f[q[l+1]] - b[q[l+1]]^2) / (b[q[l]] - b[q[l+1]]) <= 2 * a[i],则将队头出列,直至不满足
    2.此时对于队头,就是当前状态的最优决策,取出队头进行转移
    3.检查队尾,对于队尾q[r - 1], q[r],以及新决策i,若不满足斜率单调递增,则出队,直到满足
    4.将决策i插入队尾

    //可能有部分出入和错误,因为题解太长而我又是记事本写的....

    //建议读代码

     1 #include<bits/stdc++.h>
     2 #define ll long long
     3 #define ld long double
     4 #define db double
     5 using namespace std;
     6 const int maxn = 50010;
     7 ld f[maxn], s[maxn];//, a[maxn], b[maxn];
     8 int n, L;
     9 int l, r, q[maxn];
    10 
    11 inline ld a(int i) {
    12 return s[i] + i;}
    13 
    14 inline ld b(int i) {
    15 return a(i) + L + 1;}
    16 
    17 inline ld X(int i) {
    18 return b(i);}
    19 
    20 inline ld Y(int i) {
    21 return f[i] + b(i) * b(i);}
    22 
    23 inline ld ask(int i,int j) {
    24 return (Y(i) - Y(j)) / (X(i) - X(j));}
    25 
    26 int main() {
    27     cin >> n >> L;
    28     for(int i = 1; i <= n; ++i) {
    29         cin >> s[i];
    30         s[i] += s[i - 1];
    31     }
    32     l = r = 1;
    33     for(int i = 1; i <= n; ++i) {
    34         while(l < r && ask(q[l], q[l + 1]) < 2 * a(i)) l++;
    35         f[i] = f[q[l]] + (a(i) - b(q[l]))* (a(i) - b(q[l]));
    36         while(l < r && ask(i, q[r]) < ask(q[r], q[r - 1])) r--;
    37         q[++r] = i;
    38     }
    39     cout << (ll)f[n] << '
    ';
    40     return 0;
    41 }
    View Code
  • 相关阅读:
    jquery ajax 赋值问题, 后面程序判断逻辑用
    jquery formValidator 表单验证插件, ajax无法传值到后台问题的解决
    学习写了一个点击按钮倒计时的jquery小插件
    点击按钮复制指定代码
    discuz 修改积分策略( 在周期中添加"每周" )
    php获取本周周一、周日时间,上周周一、周日时间,本月第一天,本月最后一天,上个月第一天,最后一天时间
    php用正则判断是否为数字
    discuz 标签详解
    用dwz时, 由于粗心产生的一些问题(记录方便自己查阅)
    yii mailer 扩展发送邮件
  • 原文地址:https://www.cnblogs.com/ywjblog/p/9296447.html
Copyright © 2020-2023  润新知