2019 南京网络赛 I. Washing clothes(思维 + 李超树(模板))

计蒜客题目链接

在这里插入图片描述


题目大意:有 N 个人要洗衣服,每个人到来的时间是 t i t_i ti,有一台洗衣机,洗衣机洗衣服要花费 x x x 单位时间(x 是个变量),且洗衣机一次只能供一个人使用,还可以选择手洗,手洗将花费 y y y 单位时间,求如何安排可以使得最后所有人洗完衣服的时间最短,对每个 x ∈ [ 1 , y ] x \in [1,y] x[1,y] 输出所有人洗完衣服的最短时间。

设所有人到来的时间从小到大有序,在最优策略下一定存在一个分界点,该分界点之前所有人都是手洗,该分界点(包括这个分界点)之后所有人都使用洗衣机。

感性的证明一下这个结论:
所有人都不使用洗衣机的情况下,答案 ans = tn + y,答案受限于最后一个人,考虑使用洗衣机,要将答案降下来,必须先给最后手洗的人使用。

考虑从后往前第一个不使用洗衣机的人 p,对于 p 以前的点,再安排洗衣机的使用也无法使得答案降下来,只有考虑让 p p p 也使用洗衣机。
p p p 使用洗衣机后有两种可能的结果:
1、最后的答案减小,这种情况仅发生在此时答案受限于 p 点,且 p 点使用洗衣机后不会抬高最后的时限,这种情况让 p 使用洗衣机更优。
2、最后的答案增大,这种情况发生在答案受限于 p p p 后面的点,p 点使用洗衣机抬高了后面的点开始使用洗衣机的时间,这种情况不如让 p 手洗。

综上分析最后一定是一个后缀使用洗衣机。

m m m 个使用洗衣机的人,对最后的答案的贡献是 t [ m ] + ( n − m + 1 ) ∗ x t[m] + (n - m + 1) * x t[m]+(nm+1)x,使用洗衣机的人对答案的贡献为 m a x ( t [ i ] + ( n − m + 1 ) ∗ x ) max(t[i] + (n - m + 1) * x) max(t[i]+(nm+1)x)

设界限为点 p, a n s = f ( p ) = m a x ( t [ p − 1 ] + y , max ⁡ i = p n ( t [ p ] + ( n − p + 1 ) ∗ x ) ) ans = f(p) = max(t[p - 1] + y,\displaystyle\max_{i = p}^n(t[p] + (n - p + 1)*x)) ans=f(p)=max(t[p1]+y,i=pmaxn(t[p]+(np+1)x))
f ( i ) f(i) f(i) 是一个以 i 为自变量 的凸函数。显然当 x x x 增大时,界限 p (极小值点) 将会左移。

考虑倒序枚举 x,界限点单调从 n 向 1 移动,设当前界限点为 i,若 i - 1 点答案更小,则界限向 i - 1 移动。对于 m a x ( t [ i ] + ( n − m + 1 ) ∗ x ) max(t[i] + (n - m + 1) * x) max(t[i]+(nm+1)x),这是一个一次函数,可以利用李超树维护,每当界限移动时将直线维护到李超树中,由于每条直线的定义域都相同,维护直线的复杂度为 log ⁡ y \log y logy

官方题解:
在这里插入图片描述

整体复杂度为: O ( ( n + y ) log ⁡ y ) O((n + y) \log y) O((n+y)logy)


#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int N = 1e6;
typedef long long ll;
ll inf = 123456789123456789ll;
int n,m,q,op,s,t[maxn],y;
ll a,b,ans[maxn];
struct Line {
    
    				//直线结构体 
	int k,b;				 
	Line() {
    
    }
	Line(int ki,int bi) {
    
    
		k = ki, b = bi;
	}
	ll calc(int x) {
    
    	//计算在 x 点的 y值 
		return 1ll * k * x + b;
	}
};
struct seg_tree {
    
    					//维护 x = k 处最低线段 
	#define lson rt << 1,l,mid
	#define rson rt << 1 | 1,mid + 1,r
	Line line[maxn << 2];
	void build(int rt,int l,int r) {
    
    
		line[rt].k = 0; line[rt].b = 0;
		if (l == r) return;
		int mid = l + r >> 1;
		build(lson); build(rson);
	}
	void update(int rt,int l,int r,int L,int R,Line t) {
    
    
		if (L <= l && r <= R) {
    
    
			int mid = l + r >> 1;
			if (line[rt].calc(l) < t.calc(l) && line[rt].calc(r) < t.calc(r)) {
    
    		
				line[rt] = t;
			} else if (line[rt].calc(l) < t.calc(l) || line[rt].calc(r) < t.calc(r)) {
    
    		
				if (line[rt].calc(mid) < t.calc(mid)) {
    
    					
					Line tmp = t; t = line[rt]; line[rt] = tmp;
				}
				if (t.k > line[rt].k) {
    
    						
					update(rson,L,R,t);
				} else {
    
    									
					update(lson,L,R,t);
				}
			}
		} else {
    
    
			int mid = l + r >> 1;
			if (L <= mid) update(lson,L,R,t);
			if (mid + 1 <= R) update(rson,L,R,t);
		}
	}
	ll query(int rt,int l,int r,int v) {
    
    		//查询区间 L,R 最小值 
		if (l == r) return line[rt].calc(v);
		ll ans = line[rt].calc(v);
		int mid = l + r >> 1;
		if (v <= mid) return max(ans,query(lson,v));
		else return max(ans,query(rson,v)); 
	}
}seg;
int main() {
    
    
	while(scanf("%d%d",&n,&y) != EOF) {
    
    
		for (int i = 1; i <= n; i++)
			scanf("%d",&t[i]);
		t[0] = -y;
		sort(t + 1,t + n + 1);
		int p = n;								//最后一个使用洗衣机的人 
		seg.build(1,1,y);
		for (int i = y; i >= 1; i--) {
    
    
			while (p >= 1) {
    
    
				ll curval = seg.query(1,1,y,i);
				ll curans = max(curval,1ll * t[p] + y);
				ll nxtval = max(curval,1ll * (n - p + 1) * i + t[p]);
				ll nxtans = max(nxtval,1ll * t[p - 1] + y);
				if (nxtans <= curans) seg.update(1,1,y,1,y,Line(n - p + 1,t[p])), p--;
				else break;
			}
			ans[i] = max(seg.query(1,1,y,i),1ll * t[p] + y);
		}
		for (int i = 1; i <= y; i++)
			printf("%lld%s",ans[i],i == y ? "\n" : " ");	
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104638475