题目
n(n<=1e6)个人洗衣服,第i个人的到达时间为ti(0<=ti<=1e9),
有一台洗衣机,同时只能洗一件衣服,花费的时间为x,
每个人都可以手洗衣服,多个人可以同时手洗衣服,花费的时间为y,
对于x∈[1,y]的每个x,输出能让n个人都能洗完衣服所需花费的最小时间
多组样例,但保证sumN<=1e6,sumy<=1e6
思路来源
题解
以上图片来自思路来源,首先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;
}