UVALive 6694 - Toy Boxes(思维+二分)

题目链接:
UVALive 6694 - Toy Boxes
题意:
n 个物品,需要分在三个箱子里,每个箱子的花费是((箱子重量忽略不计),求总花费最少是多少? (n20000,1000)
分析:
先考虑将其分为两堆的情况。我们需要证明一个结论:分成两堆时一定是连续小的物品在一堆,连续大的在一堆,也就是其中一堆的任意一个物品重量都要小于另一堆。,并且小的那一堆的个数一定要大于大的那一堆的个数。
假设一堆的个数为 x ,重量为A,另一堆的个数为 y ,重量为B,如果第一堆的每一件物品 xi 的重量都小于第二堆的任意一件物品 yj ,并且 x>y 那么就是“稳定”的了。否则:我们可以通过交换 xiyj 使得总花费更少。

  • x<y 且第一堆的每一件物品 xi 的重量都小于第二堆的任意一件物品 yj
    从第二堆中任取一个 yj ,重量为 b 放进第一堆中
    原有的总花费为:

    xA+yB

    调整后:

    (x+1)(A+b)+y1)(Bb)=xA+yB+b(xy)+A+bB+b=xA+yB+b(xy)+(2b(BA))

    因为 x<y 且第一堆的每一件物品 xi 的重量都小于第二堆的任意一件物品 yj ,那么 BA>b,2b(BA)<b ,同时 xy<1,b(xy)<b ,那么
    b(xy)+(2b(BA))<0

    所以调整后更优。

  • x>y 且第一堆的每一件物品 xi 的重量 a 大于第二堆的一件物品yj重量 b
    原有的总花费是:

    xA+yB
    ,
    交换后的花费是:

    x(Aa+b)+y(Bb+a)=xA+yB+(yx)(ab)

    显然调整后更优。

这样子可以粗略的证明了上述结论。对于分成两堆的情况,先排序然后扫一遍即可。那么分成三堆呢?我们可以枚举第一堆的个数 i ,然后二分查找第二堆的个数,因为花费关于个数是一个单峰函数。比较midmid+1的花费情况。

注意开 long long

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <climits>
#include <ctime>
using namespace std;
typedef long long ll;
const int MAX_N = 20010;

int T, n;
ll data[MAX_N], sum[MAX_N];

inline ll solve(int t, int x) //t是第二堆的个数
{
    int a = t, b = n - (x + t);
    ll A = sum[x + a] - sum[x], B = sum[n] - sum[x + a];
    return A * a + B * b;
}

inline ll check(int x)
{ 
    int low = 1, high = n - x - 1, mid;
    ll tmp1, tmp2; 
    while(low < high - 10) {
        mid = (low + high) / 2;
        tmp1 = solve(mid, x);
        ++mid;
        tmp2 = solve(mid, x);
        if(tmp1 > tmp2) low = mid;
        else high = mid - 1;
    }
    ll res = min(solve(low, x), solve(high, x));
    for(int i = low + 1; i < high; ++i) {
        res = min(res, solve(i, x));
    }
    return res;
}

int main()
{
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%lld", &data[i]);
        sort(data + 1, data + n + 1);
        for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + data[i];
        ll ans = sum[n] * n;
        for(int i = 1; i <= n - 2; ++ i) {
            ans = min(ans, sum[i] * i + check(i));
           // printf("i = %d ans = %d\n", i, ans);
        }
        if(n <= 3) ans = sum[n];
        printf("%lld\n", ans);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值