P3195 [HNOI2008]玩具装箱TOY

19 篇文章 0 订阅

P3195 [HNOI2008]玩具装箱TOY


题意

  • n个物品,每个物品长度c[i]

  • 将物品分为连续的几组,每组内部每个物品用空间1隔开

    一组物品的长度为 ∑ i i < = j c [ i ] + j − i \sum_{i}^{i<=j}c[i] + j-i ii<=jc[i]+ji

  • 每组物品的价值为 ( 长 度 − L ) 2 (长度-L)^2 (L)2L为常数


思路

状态

dp[i]:表示加入i后,最优分组的代价

状态转移方程

f [ i ] = m i n ( s u m [ i ] − s u m [ j ] − L ) 2 + f [ j ] f[i] = min(sum[i] - sum[j] - L) ^ 2 + f[j] f[i]=min(sum[i]sum[j]L)2+f[j]

斜率优化

我们发现sum[j]有个关于i的系数,所以我们尝试一下能不能斜率优化

f [ i ] = m i n ( s u m [ i ] − s u m [ j ] − L ) 2 + f [ j ] f[i] = min(sum[i] - sum[j] - L) ^ 2 + f[j] f[i]=min(sum[i]sum[j]L)2+f[j]

f [ i ] = s u m [ i ] 2 + ( s u m [ j ] + L ) 2 − 2 ∗ s u m [ i ] ∗ ( s u m [ j ] + L ) + f [ j ] f[i] = sum[i] ^ 2 + (sum[j] + L) ^ 2 - 2 * sum[i] * (sum[j] + L) + f[j] f[i]=sum[i]2+sum[j]+L22sum[i]sum[j]+L+f[j]
f [ j ] + ( s u m [ j ] + L ) 2 = 2 ∗ s u m [ i ] ∗ ( s u m [ j ] + L ) + f [ i ] − s u m [ i ] 2 f[j] + (sum[j] + L) ^ 2 = 2 * sum[i] * (sum[j] + L) + f[i] - sum[i] ^ 2 f[j]+(sum[j]+L)2=2sum[i](sum[j]+L)+f[i]sum[i]2

x : s u m [ j ] + L x: sum[j] + L x:sum[j]+L y : f [ j ] + ( s u m [ j ] + L ) 2 y : f[j] + (sum[j] + L) ^ 2 y:f[j]+(sum[j]+L)2 只与j有关;

k : 2 ∗ s u m [ i ] k: 2 * sum[i] k:2sum[i] 只与i有关,且与dp[i] 无关;

b : f [ i ] − s u m [ i ] 2 b: f[i] - sum[i] ^ 2 b:f[i]sum[i]2dp[i] 直接挂钩

现在,我们按照上述的 xy 建立一个平面直角坐标系。则我们需要最小化 f[i] ,就是最小化该函数的截距(截距:就是 y = k x + b y=kx+b y=kx+b 中的 b )。

由于 k = 2 ∗ s [ i ] k=2*s[i] k=2s[i]的值不随 j 的变化而变化,因此该函数图像的斜率一定。这时我们可以把选择状态的过程想象为一条斜率一定的直线在坐标系里向上平移的过程。而因为每个不同的 j会带来不同的 xy 的取值,因此每个决策点都对应了平面直角坐标系里的一个点。为了使我们这条函数图像的截距最小,我们在向上平移的过程中一定先选第一个碰到的点

pic1

单调队列维护凸壳

在本题中,有两个隐藏的条件:

  1. 横坐标 x ,即 ( s [ j ] + L ) (s[j]+L) (s[j]+L)递增。
  2. 相邻两点的线段斜率,即 2 ∗ s [ i ] 2*s[i] 2s[i]也是递增的。

因此我们可以用一个单调队列来维护前文所述的这样一个“凸壳”。对于每个状态变量 i

  1. 检查队头的两个决策变量 q[l]q[l+1],若他们的斜率 Y ( q [ l + 1 ] ) − Y ( q [ l ] ) X ( q [ l + 1 ] ) − X ( q [ l ] ) < = k \frac{Y(q[l+1])-Y(q[l])}{X(q[l+1])-X(q[l])}<=k X(q[l+1])X(q[l])Y(q[l+1])Y(q[l])<=k(这里 kxy 就是前面改写状态转移方程得到的),则将队头 q[l]弹出。
  2. 直接取出队头 j = q [ l ] j=q[l] j=q[l]为最优决策,计算出 f[i]
  3. 把新决策 i从队尾插入。在插入之前,若三个决策点 q[r-1]q[r]i 不满足斜率单调递增(不满足下凸性,即 q[r]是无用决策),则直接从队尾让 q[r]出队,继续检查新的队尾。

由于每个元素最多入队、出队一次,因此整个算法的时间复杂度为 O(n)


代码

const int maxn = 50005;
typedef long long LL;
typedef pair<LL, LL> pll;
LL sum[maxn];
pll q[maxn];
int main()
{
    int n;
    LL L;
    scanf("%d%lld", &n, &L);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &sum[i]);
    for (int i = 1; i <= n; i++)
        sum[i] += sum[i - 1];
    //f[i]=min(sum[i]-sum[j]+i-(j+1)-L)^2+f[j];
    L++;
    for (int i = 1; i <= n; i++)
        sum[i] += i;
    // k: 2 * sum[i] 只与i有关,且与dp[i] 无关;
    // b: f[i] - sum[i] ^ 2 与dp[i] 直接挂钩
    int l = 1, r = 1;
    register LL x, y, k, b;
    q[1] = make_pair(L, L * L);
    for (int i = 1; i <= n; i++) {
        k = 2 * sum[i];
        //(y2-y1)/(x2-x1) <= k
        while (l < r && (q[l + 1].second - q[l].second) * 1.0 / (q[l + 1].first - q[l].first) <= k)
            l++;
        b = q[l].second - k * q[l].first;
        x = sum[i] + L;
        y = b + sum[i] * sum[i] + x * x;
        //(y3-y1)/(x3-x1) < (y2-y1)/(x2-x1)
        while (l < r && (y - q[r - 1].second) * 1.0 / (x - q[r - 1].first) <= (q[r].second - q[r - 1].second) * 1.0 / (q[r].first - q[r - 1].first))
            r--;
        q[++r] = make_pair(x, y);
    }
    printf("%lld\n", q[r].second - q[r].first * q[r].first);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值