【AGC053D】Everyone is a winner(贪心)(凸包)

Everyone is a winner

题目链接:AGC053D

题目大意

有 n 个人 n 道题目,每个人做其中 ai 个题目需要 1 分钟,bi 个题目需要 2 分钟,ci 个题目需要 3 分钟。
需要你给每个人安排做每道题用的时间,使得第 i 个人是最早做完 i 道题目的人之一。
问你是否能安排出合法的方案。

思路

发现如果我们确定了前 i i i 步做什么,剩下的做法是显然的:
就是不要太快,所以是从大到小放。
那前面的也是不能太快,因为你最终到 i i i 的时间已经固定,所以我们也是从大到小放。

于是有这么一个猜想过程,我们让 f i , j f_{i,j} fi,j i i i 这个人做了 j j j 题目需要的时间,然后我们有一个限制 T i T_i Ti 就是前 i i i 道题最早被解决掉的时间(在考虑的人之中)。

那先考虑后面的怎么放(从后面往前看是因为最后的位置放的是确定的),那首先是肯定给前面的留空间,所以我们最大化做完 i i i 题需要的时间,接着我们让 3 3 3 尽可能的多(这样不容易比别人快)

那求出上面的 f f f 之后,我们可以给 T i T_i Ti 更新限制: T y = min ⁡ ( T y , t x , y ) ( 1 ⩽ y < x ) T_y=\min(T_y,t_{x,y})(1\leqslant y<x) Ty=min(Ty,tx,y)(1y<x)
那我们给前面的留了空间,但是后面的呢?它会不会因此比后面的更快呢?

我们其实可以通过证明发现在只有 1 , 2 , 3 1,2,3 1,2,3 这种耗时的情况下,是不会有的。
考虑分类讨论,如果我们有 T i + 2 ⩾ T i + 1 T_i+2\geqslant T_{i+1} Ti+2Ti+1(就是这前面 i + 1 i+1 i+1 个不是都填 3 3 3,因为如果不是,至少会有 2 / 1 2/1 2/1 在最后)
那我们扩展一下对于 j > i , T i + 2 ( j − i ) ⩾ T j j>i,T_i+2(j-i)\geqslant T_j j>i,Ti+2(ji)Tj
那自然合法。

那如果是都填 3 3 3,那对于前面的一段都填 3 3 3 的,那比它编号小的人要做到前面的某个时刻比他小或者跟它一样,这个时刻又要比它大或者一样,所以就也是全 3 3 3,那后面的数也是一个道理,那这 n n n 个人的前 i + 1 i+1 i+1 个都是 3 3 3,那也就没啥事了。
后面出现了没有 3 3 3,那又是上面的情况了。

所以是对的!

那我们就用一个斜率为 1 / 2 / 3 1/2/3 1/2/3 的凸包来维护 T T T 这个东西。
然后这里的 T T T 其实我们减可以直接一个一个减,因为这个东西是递减的,均摊下来只会减 O ( n ) O(n) O(n) 次。

然后就是看给你 T T T,要你看是否合法:
考虑化一下式子得到一些不等式:
x + y + z = i x+y+z=i x+y+z=i
x + 2 y + 3 z = t x+2y+3z=t x+2y+3z=t
(解二元一次方程,以 y , z y,z y,z 为未知数)
y + z = i − x , 2 y + 3 z = t − x y+z=i-x,2y+3z=t-x y+z=ix,2y+3z=tx
y = − 2 x − t + 3 i y=-2x-t+3i y=2xt+3i
z = x + t − 2 i z=x+t-2i z=x+t2i

x = z − t + 2 i = 3 i − t − y 2 x=z-t+2i=\dfrac{3i-t-y}{2} x=zt+2i=23ity
0 ⩽ x ⩽ a i , 0 ⩽ y ⩽ b i , 0 ⩽ z ⩽ c i 0\leqslant x\leqslant a_i,0\leqslant y\leqslant b_i,0\leqslant z\leqslant c_i 0xai,0ybi,0zci(这是条件,两个极值分别带入)
2 i − t ⩽ x ⩽ c i + 2 i − t , 3 i − t − b i + 1 2 ⩽ x ⩽ 3 i − t 2 2i-t\leqslant x\leqslant c_i+2i-t,\dfrac{3i-t-b_i +1}{2}\leqslant x\leqslant \dfrac{3i-t}{2} 2itxci+2it,23itbi+1x23it
(注意第二个式子的多边界,要在上面 + 1 +1 +1,因为它上面 x = x= x= 的位置不能直接这么看,你真正要满足的是上面方程解出来的结果,所以要记得 + 1 +1 +1,不加样例也过不了)

代码

#include<cstdio>
#include<iostream>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

const int N = 2e5 + 100;
int n, a[N], b[N], c[N];

struct Line {
	int a, b, c;//斜率(填1/2/3)对应的截距 
	
	Line(int aa = INF, int bb = INF, int cc = INF) {
		a = aa; b = bb; c = cc;
	}
	
	int get_val(int x) {
		return min(min(a + x, b + 2 * x), c + 3 * x);
	}
}T;

Line merge(Line x, Line y) {
	return Line(min(x.a, y.a), min(x.b, y.b), min(x.c, y.c));
} 

void slove() {
	scanf("%d", &n); T = Line();
	for (int i = 1; i <= n; i++) {
		scanf("%d %d %d", &a[i], &b[i], &c[i]);
		T = merge(T, Line(b[i] + 2 * c[i], c[i], 0));
	}
	bool yes = 1;
	for (int i = n; i >= 1; i--) {
		int t = T.get_val(i), x;
		while (t >= 0) {
			x = min(a[i], min(c[i] + 2 * i - t, (3 * i - t) / 2));
			if (x >= max(0, max(2 * i - t, (3 * i - t - b[i] + 1) / 2))) break;
			t--;
		}
		if (t < 0) {yes = 0; break;}
		int y = -2 * x - t + 3 * i, z = x + t - 2 * i;
		T = merge(T, Line(y + 2 * z, z, 0));
	}
	if (yes) printf("Yes\n");
		else printf("No\n");
}

int main() {
	int T; scanf("%d", &T);
	while (T--) slove();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值