2019 ICPC南京网络赛 I Washing clothes (李超树)

题目描述:

N persons are about to engage in their favorite activity doing laundry together! The i-th person will come at t i t_i ti

minute. Unfortunately, there is only one washing machine, which takes x x xminutes to wash one load of laundry. At any moment, the machine could only be processing at most one person’s clothes. But this couldn’t dispel their enthusiasm for laundry! They may also choose to wash clothes by hand, which takes y y y minutes. Of course, everyone has hands. So they can wash clothes at the same time by hand.

​ For different integer x x x, help them to calculate the minimal moment that all N loads of clothes have been washed! Obviously, if x > y x\gt y x>y, it’s not necessary to use washing machine. So, you only need to calculate the answer for x ∈ [ 1 , y ] x \in [1,y] x[1,y].

题目大意:

有n个人,各有一个到达的时刻,到达之后它们可以选洗衣机或者手洗,洗衣机花费时间为x,手洗花费时间是y。手洗可以同时进行,但是洗衣机同一时刻只能处理一个人的衣服。问对于每一个 x ∈ [ 1 , y ] x\in[1,y] x[1,y],最后一个人洗完衣服的最小时刻。

解题思路:

这题需要注意到:如果让一个人手洗,那么所有在这个人之前到达的人都可以选手洗。那么也就是说,对于特定的x,最优策略一定可以是这样:以一个人 j j j为分界线, j j j用洗衣机,在 j j j之前的所有人都手洗,在 j j j之后的人全部机洗。

证明:要么全部用洗衣机洗,这个j就是1,要么一定存在一个最后一个用手洗的人 j j j,那么 j j j之前的人不论是用手洗还是用机洗都无所谓,所以让它们全部手洗。因为 j j j是最后一个用手洗的,所以它后面的全是机洗。

那么如果 j j j固定,答案是手洗的结束时间和机洗的结束时间中较大的。用 g ( i ) g(i) g(i)表示从第i个人开始后面全用洗衣机时,手洗的结束时间。 f ( i ) f(i) f(i)表示从第i个人开始后面全用洗衣机时,机洗的结束时间。那么:
g ( i ) = t i − 1 + y g(i)=t_{i-1}+y g(i)=ti1+y
f ( i ) = m a x ( t j + ( n − j + 1 ) x ) ( j > = i ) f(i)=max(t_j+(n-j+1)x)_{(j>=i)} f(i)=max(tj+(nj+1)x)(j>=i)
答案= m a x ( g ( i ) , f ( i ) ) max(g(i),f(i)) max(g(i),f(i))
显然, g ( i ) g(i) g(i)随着i递增, f ( i ) f(i) f(i)随着i递减。答案是一个凸函数,有极小值点。而 f ( i ) f(i) f(i)函数,是随着x的增加而递增的,所以x增加时极小值点会右移,我们枚举x,维护这个极小值点即可。
下面考虑x确定的时候,如何快速得到 f ( i ) f(i) f(i)的值。可以看到, f ( i ) f(i) f(i)是多个关于x的一元函数求最大值。我们可以用李超树维护多个线段,并实现 l o g ( n ) log(n) log(n)时间内求单点最值。但是李超树只支持加入线段操作,所以我们考虑从大到小枚举x,每次向左边移动极小值,同时加入线段。每次求得的极小值就是答案。
ac代码:

#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e6 + 50;
ll t[maxn];
ll k[maxn], b[maxn];
ll f(int cur, int x){return k[cur]*x + b[cur];}//函数值计算
int id[maxn*4];//树上记录的线段
void build(int rt, int l, int r, int cur){id[rt] = cur;if(l == r) return; build(lson, cur), build(rson, cur);}
void update(int rt, int l, int r, int cur){
    int pre = id[rt];
    //cout<<"l:"<<l<<" r:"<<r<<endl;
    if(l == r){
        if(f(pre, l) < f(cur, l)) id[rt] = cur;
        return;
    }
    if(k[cur] > k[pre]){
        if(f(cur, mid) > f(pre, mid))
            id[rt] = cur, update(lson, pre);
        else
            update(rson, cur);
    }
    else{
        if(f(cur, mid) > f(pre, mid))
            id[rt] = cur, update(rson, pre);
        else
            update(lson, cur);
    }
}
ll qry(int rt, int l, int r, int x)
{
    //cout<<"l:"<<l<<" r:"<<r<<" k:"<<k[id[rt]]<<" b:"<<b[id[rt]]<<endl;
    ll ans = f(id[rt], x);
    if(l == r) return ans;
    if(x <= mid) ans = max(ans, qry(lson, x));
    else ans = max(ans, qry(rson, x));
    return ans;
}
int n;
ll y;
void init()
{
    t[0] = -y;
    for(int i = 1; i <= n; ++i) scanf("%lld", &t[i]);
    sort(t, t+n+1);
    for(int i = 0; i <= n; ++i){
        k[i] = n-i+1; b[i] = t[i];
    }
}
ll ans[maxn];
void sol()
{
    build(1, 0, y, n);//一开始存在线段n
    //cout<<"?"<<qry(1, 0, y, 3)<<endl;
    int p = n-1;
    for(int x = y; x > 0; --x){
        ll cur = max(t[p] + y, qry(1, 0, y, x));
        ll res = qry(1, 0, y, x);//所有加入的线段中f(x)最大值
        while(p > 0 && max(t[p-1]+y, max(res,f(p, x)) ) <= cur){
            update(1, 0, y, p); //加入线段
            res = max(res, f(p, x));
            cur = max(t[p-1]+y, res);//更新当前值
            p--;
        }
        //cout<<"id: "<<id[1]<<"x:"<<x<<" p:"<<p<<" ?:"<< qry(1, 0, y, x)<<endl;
        //cout<<"?"<<qry(1, 0, y, 3)<<endl;
        ans[x] = cur;
    }
    for(int i = 1; i <= y; ++i){
        if(i > 1) printf(" ");
        printf("%lld", ans[i]);
    }printf("\n");
}
int main()
{
	while(scanf("%d%lld", &n, &y) != EOF)
    {
        init();sol();
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值