题意:
给出 和 ,表示 个人以及手洗的时间为 ,以及 个 ,表示每个人达到洗衣房的时间。每个人可以选择手洗或者机洗,但只有一个洗衣机,问对于机洗时间为 时的最后一个人洗完的最小时间,输出 为 时的答案。
思路:
首先,我们要将问题简化,假设机洗时间为一个固定值 ,我们如何求解?
我们首先将 数组排序,稍加模拟应该可以发现,达到最优情况时,一定存在一个分界点 ,分界点前面的人都是手洗,分界点之后的人都是机洗,即手洗和机洗的人都是连续的。
然后继续观察可以发现,一个人如果手洗,则其对最终答案的影响就是 。如果一个人机洗,则其对最终答案的影响并不是 。因为一旦这个人选择机洗,意味着之后的人都需要机洗,因此对答案的贡献应该是 。
每个人对答案的贡献如果不明白的话,可以继续看这一段解释。如果第 个人选择了机洗,则第 个人洗完的时间是 。那么对于第 个人来说,他洗完的时间是 。对于第 来说,他洗完的时间是 。以此类推,到第 个人洗的时候,第 个人选择机洗对答案的贡献就是 。
到这里我们可以发现,对于每个人来说,其手洗对答案的影响是 ,而其机洗对答案的影响是 ,因此我们取两条直线的较小部分作为这个人对最终答案的影响。
所以我们发现每个人对最终答案的影响都是一个分段函数,因此我们令 ,即机洗时间作为横坐标,然后将每一个人的分段函数加到整个坐标上。然后对于每一个 ,求取这个位置上方的最大值作为答案即可。
如果对李超树基础内容不了解的话,可以查看这篇文章。
总结:
[比赛反思]
比赛的时候因为李超树求线段交点的地方用了 , 了一整场,赛后才找出 ,真的实锤憨憨…
现在更新,李超树根本不需要求交点…
[题目总结]
对于一道较为复杂的题目,解决这道题的关键点在于不断地观察与挖掘题目的一些性质,并且敢于提出错误做法以及 做法。正解不是一蹴而就的,而是对当前算法不断的修改与优化最终才能达到正解。
代码:
#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const ll inf = 1e15;
const int N = 1e6+100;
const long double EPS = 1e-8;
using namespace std;
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 n,y;
ll a[N];
inline int get_id(int l,int r) {return (l+r) | (l!=r);}
//计算某条线段在某一个横坐标的纵坐标值
inline ll calc(line a,ll pos) {return a.k*pos+a.b;}
void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
void build(int l,int r){
int now = get_id(l,r);
sgt[now].flag = 0; sgt[now].l = sgt[now].r = 0; sgt[now].k = sgt[now].b = 0;
if(l == r) return;
int mid = (l+r)>>1;
build(l,mid); build(mid+1,r);
}
void modify(int l,int r,line k){
int now = get_id(l,r);
if(k.l <= l && r <= k.r){
if(!sgt[now].flag) sgt[now] = k, sgt[now].flag = 1; //原区间没有优势线段
else if(calc(k,l) >= calc(sgt[now],l) && calc(k,r) >= calc(sgt[now],r)) sgt[now] = k; //原区间优势线段被覆盖
else if(calc(k,l) > calc(sgt[now],l) || calc(k,r) > calc(sgt[now],r)){ //有一端比原线段大
int mid = (l+r)>>1;
if(calc(k,mid) > calc(sgt[now],mid)){ //新线段比原来优势线段坐标高
swap(k,sgt[now]);
}
if(calc(k,l) > calc(sgt[now],l)) modify(l,mid,k);
else modify(mid+1,r,k);
}
}
else{
if(l == r) return;
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));
}
}
void init(){
build(1,y);
rep(i,1,n){
int pos1 = y/(n-i+1), pos2 = (y%(n-i+1)==0) ? pos1:(pos1+1);
pos2 = max(pos2,1);
pos1 = min(pos1,y);
line tmp1(n-i+1,a[i],1,pos1,1),tmp2(0,a[i]+y,pos2,y,1);
if(pos1 > 0) modify(1,y,tmp1);
if(pos2 <= y) modify(1,y,tmp2);
}
}
int main()
{
while(~scanf("%d%d",&n,&y)){
rep(i,1,n) scanf("%lld",&a[i]);
sort(a+1,a+1+n);
init();
rep(i,1,y){
ll ans = query(1,y,i);
printf("%lld%c",ans," \n"[i==y]);
}
}
return 0;
}