The Preliminary Contest for ICPC Asia Nanjing 2019 I.Washing clothes(李超树 极值点)

题目

n(n<=1e6)个人洗衣服,第i个人的到达时间为ti(0<=ti<=1e9),

有一台洗衣机,同时只能洗一件衣服,花费的时间为x,

每个人都可以手洗衣服,多个人可以同时手洗衣服,花费的时间为y,

对于x∈[1,y]的每个x,输出能让n个人都能洗完衣服所需花费的最小时间

多组样例,但保证sumN<=1e6,sumy<=1e6

思路来源

https://blog.youkuaiyun.com/qq_41997978/article/details/104638475?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-5.channel_param

题解

在这里插入图片描述

以上图片来自思路来源,首先n个人按ti排增序,因为可以多个人同时手洗,

所以如果钦定第i个人是最后一个手洗的,则[1,i-1]个人手洗的时间一定比i结束的时间更早,

此时丢进洗衣机洗是不优的,即只用令[i+1,n]的人都机洗即可,

根据类似hall定理之类的东西,

可以知道令[i+1,n]都机洗所完成的时间是max(tj+(n-j+1)*x),

即这个时间对于每个check的时间点都成立,且为最小的那个合法时间

 

于是所求函数为g(i)=max(第i个人手洗,[i+1,n]机洗),

该函数是一个凸函数,必有一个极小值点i,

而g(i,x)<=g(i,x+1)知随着x最大极值点i右移,

即我们可以先对x=y求一个极值点,然后不断降x,

令极值点左移的过程中插入直线,李超树维护直线最大值,

类似双指针,如果左侧更小则说明还没到极值点,就可以继续往左扫

主要是要发现凸函数及单调的性质吧

代码

#include<bits/stdc++.h>
typedef long long ll;
const int N=4e6+100;
using namespace std;
int n,y,a[N];
ll ans[N];
struct line{
    ll k,b;
    int l,r,flag;
    line(){}
    line(ll a1,ll a2,int l1,int r1,int f1):k(a1),b(a2),l(l1),r(r1),flag(f1) {}
}sgt[2*N];
int get_id(int l,int r) {return (l+r)|(l!=r);}
ll calc(line a,int pos) {return 1ll*a.k*pos+a.b;}
void init(int l,int r){
	int mid=(l+r)>>1,now=get_id(l,r);
	sgt[now].flag=sgt[now].l=sgt[now].r=sgt[now].k=sgt[now].b=0;
	if(l==r){
		return;
	}
	init(l,mid);
	init(mid+1,r);
}
void modify(int l,int r,line k){
    int now = get_id(l,r);
    if(k.l <= l && k.r >= r){
        if(!sgt[now].flag) sgt[now] = k, sgt[now].flag = 1; //原区间没有优势线段
        else if(calc(k,l)-calc(sgt[now],l) > 0 && calc(k,r)-calc(sgt[now],r) > 0) sgt[now] = k; //原区间优势线段被覆盖
        else if(calc(k,l)-calc(sgt[now],l) > 0 || calc(k,r)-calc(sgt[now],r) > 0){ //有一端比原线段大
            int mid = (l+r)>>1;
            //中点坐标处,新线段比原来优势线段更优
            if(calc(k,mid)-calc(sgt[now],mid) > 0) swap(k,sgt[now]);
            if(calc(k,l)-calc(sgt[now],l) > 0) modify(l,mid,k);
            else modify(mid+1,r,k);
        }
    }
    else{
        int mid = (l+r)>>1;
        if(k.l <= mid) modify(l,mid,k); //涉及了左区间
        if(mid < k.r) modify(mid+1,r,k); //涉及了右区间
    } 
}

ll query(int l,int r,int x){
    int now = get_id(l,r);
    if(l == r) return calc(sgt[now],x);
    else{
        int mid = (l+r)>>1;
        ll ans = calc(sgt[now],x);
        if(x <= mid) return max(ans,query(l,mid,x));
        else return max(ans,query(mid+1,r,x));
    }
}

int main(){
    while(~scanf("%d%d",&n,&y)){
	    for(int i=1;i<=n;++i){
	    	scanf("%d",&a[i]);
	    }
	    sort(a+1,a+n+1);
	    init(1,y);
	    a[0]=-y;//与max(-y+y,直线)=max(0,直线)=直线情况统一 
		int pos=n-1;
		line tmp(1,a[n],1,y,1);
		modify(1,y,tmp);
		for(int x=y;x>=1;--x){
			ll now=max(1ll*a[pos]+y,query(1,y,x));//凸函数 
	    	while(pos>=1){
	    		ll pre=max(1ll*a[pos-1]+y,max(query(1,y,x),a[pos]+1ll*(n+1-pos)*x));
	    		if(now>=pre){//判断极值点能否左移 能左移则左移并插入直线 
	    			line tmp(n+1-pos,a[pos],1,y,1);
	    			modify(1,y,tmp);
	    			pos--;
	    			now=pre;
	    		}
	    		else{
	    			break;
	    		}
	    	}
	    	ans[x]=now;
	    } 
	    for(int i=1;i<=y;++i){
			printf("%lld%c",ans[i]," \n"[i==y]);
		} 
	}
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值