2019 ICPC南京网络预选赛 I Washing clothes 李超线段树
题意:有n个人,每个人有一件衣服需要洗,可以自己手洗花费t时间,也可以用洗衣机洗,但是洗衣机只有一台,即每个时刻最多只能有·一个人用洗衣机洗衣服。现在给你每个人最早可以开始洗衣服的时间,问当洗衣机的洗衣时间分别为1, 2....t的时候洗完所有衣服的最短时间。
思路:首先容易想到我们先把所有人按照洗衣服的时间排序。我们发现,最终洗衣服时间的瓶颈肯定在于后面的人,所以我们考虑怎么使用洗衣机使得总的洗衣时间最短。首先最后一个人是一定要用洗衣机洗衣服的,因为洗衣机的洗衣服时间 <= t, 那么最后一个人用洗衣机洗衣服不会让总时间变长,只可能变短。同理,如果最后一个人用洗衣机和倒数第二个人不冲突的话,倒数第二个人肯定也用洗衣机了。如果倒数第二个人用不了洗衣机,那么前面的人也没必要用洗衣机了,因为倒数第二个人的洗衣时间肯定大于等于前面的人,最后时间的瓶颈只可能在倒数第二个人身上或者倒数第一个人身上了。以此类推,我们可以发现。最后用洗衣机的人的序列肯定是原序列的一个后缀。那么,对于当前的x,怎么确定哪些人用洗衣机呢?设b[i]为第i个人开始洗衣服的时间,假设是从第i个人开始用洗衣机洗衣服,时间限制可能是两种:1:来自第i - 1个人手洗的时间限制:b[i - 1] + t。2:后面的人用洗衣机的时间的结束时间。这个结束时间怎么算呢?我们假设从第i个人开始用洗衣机,最理想情况是他一用完洗衣机别人就可以用,那么时间是b[i] + (n - i + 1) * x, 我们把i及其以后的所有人的理想状态时间都算出来,取最大值就是实际的结束时间。那么我们发现从i开始的人用洗衣机的时间是max(b[i - 1] + t, max(b[k] + (n - k + 1) * x)) (k >= i)。容易发现,b[k] + t是个单调递增的函数, max(b[k] + (n - k + 1) * x)是个单调递减的函数,他们俩取max是一个单谷函数,最优解就是谷底。这个好像不是严格单调函数不能三分,只能暴力枚举。那么对于一个特定的x时间复杂度是O(n)的。但是现在有n个x,怎么办?我们发现,当x等于t的时候,谷底一定在最后面,随着x的不断变小,谷底会逐渐往前移动,那么我们可以用一个指针来指向谷底,每当x减一的时候判断一下谷底是不是前移了,如果是就往前移动。那么新的问题出现了,b[i - 1] + t是常数不用维护,x减一之后max(b[k] + (n - k + 1) * x))好像得全部重新计算了?如果把n - k + 1看成斜率,b[k]看成截距,那么问题就转化为了在一堆直线中当横坐标确定时找纵坐标的最大值,这个用李超线段树可以在O(log(n))的时间内做到。
代码:
#include <bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f #define ls (o << 1) #define rs (o << 1 | 1) using namespace std; const int maxn = 1000010; struct line { double k, b; }; line a[maxn]; int tot; struct node { int id; }; node tr[maxn * 4]; double get_pos(int x, int y) { return a[x].k * y + a[x].b; } double cross(int x, int y) { return (a[x].b - a[y].b) / (a[y].k - a[x].k); } void init(int o, int l, int r) { tr[o].id = 0; if(l == r) return; int mid = (l + r) >> 1; init(ls, l, mid); init(rs, mid + 1, r); } void update(int o, int l, int r, int now) { if(!tr[o].id) { tr[o].id = now; return; } int x = tr[o].id; double l1 = get_pos(now, l), r1 = get_pos(now, r); double l2 = get_pos(x, l), r2 = get_pos(x, r); if(l1 <= l2 && r1 <= r2) { return; } else if(l1 > l2 && r1 > r2) { tr[o].id = now; return; } int mid = (l + r) >> 1; double y = cross(now, x); if(y <= mid) update(ls, l, mid, r1 > r2 ? x : now); else update(rs, mid + 1, r, l1 > l2 ? x : now); if((y <= mid && r1 > r2) || (y > mid && l1 > l2)) tr[o].id = now; } int query(int o, int l, int r, int pos) { if(l == r) { return tr[o].id; } int mid = (l + r) >> 1, ans = 0; if(pos <= mid) ans = query(ls, l, mid, pos); else ans = query(rs, mid + 1, r, pos); if(!tr[o].id) return ans; int x = tr[o].id; if(get_pos(x, pos) > get_pos(ans, pos)) return x; else return ans; } int b[maxn]; LL res[maxn]; int main() { int n; LL t; while(~scanf("%d%lld", &n, &t)) { init(1, 1, t); for (int i = 1; i <= n; i++) { scanf("%d", &b[i]); } sort(b + 1, b + 1 + n); for (int i = 1; i <= n; i++) { a[i] = (line){(double)n - i + 1, (double)b[i]}; } b[0] = -t; LL tmp, tmp1; update(1, 1, t, n); int p = n - 1; for (int i = t; i >= 1; i--) { while(1) { int ans = query(1, 1, t, i); LL t1 = b[ans] + ((LL)n - ans + 1) * i; tmp = max(t1, b[p] + t); if(p == 0) break; tmp1 = max(max(t1, b[p] + ((LL) n - p + 1) * i), b[p - 1] + t); if(tmp1 <= tmp) { update(1, 1, t, p); p--; } else break; } res[i] = tmp; } for (int i = 1; i < t; i++) { printf("%lld ", res[i]); } printf("%lld\n", res[t]); } }