[ACNOI2021]《普林斯普的呼唤》卷二

220 篇文章 2 订阅
89 篇文章 0 订阅

题目

题目背景
设好魔法阵之后,漆黑的套子突然膨胀,一闭眼、一睁眼,便已进入了一个神秘的空间。四下漆黑一片,唯见脚下有两排冒着鬼火般蓝色荧光的细线,框出了一条路。

别无选择,只能沿着这条路向前走。走到末了,前有一个两人高的石碑,上面用索莫兰语写着两句话。这是最古老的语言,早在人类直立行走之前,就能在太阳居屋里听到。翻译过来,第一句话是:有的人死了,他还活着

第二句话是:你们都不是什么好东西

慢慢将手靠近石碑……忽而碰到了什么,不可见的实体,挡住了手。

“原来太阳的震颤,引来了旧神 F i r e W i n g B i r d \sf FireWingBird FireWingBird,它下了封印,想要阻止这一切。”

题目描述
旧神 F i r e W i n g B i r d \sf FireWingBird FireWingBird 下了 n n n 道封印。第 i i i 道封印,最初只需要 b i b_i bi 毫秒就可以破除;但每过 1 1 1 毫秒,它就会变得更强,用于破除的时间就会增加 a i a_i ai ;唯有在破除该封印时,它不会变强。在破除两道封印之间,你也需要 1 1 1 毫秒的时间休息。

形式化地说,在 t t t 毫秒的时刻去破除第 i i i 道封印,将会花费 a i t + b i a_it+b_i ait+bi 毫秒,然后休息 1 1 1 毫秒。在此期间不能破除其它的诅咒。

时间无多。只能选择在 T T T 毫秒内,破除尽可能多的封印。那么究竟能破除多少个呢?最初的时刻是 0 0 0 毫秒。

数据范围与提示
n ⩽ 1 0 5 n\leqslant 10^5 n105 T ⩽ 1 0 9 T\leqslant 10^9 T109 。保证 0 ⩽ a i , b i ⩽ 1 0 9 0\leqslant a_i,b_i\leqslant 10^9 0ai,bi109

思路

无论是什么做法,首先需要给封印排序。简单推导可知,排序依据是
b i + 1 a i < b j + 1 a j \frac{b_i+1}{a_i}<\frac{b_j+1}{a_j} aibi+1<ajbj+1
然后 d p \tt dp dp,发现没有办法用线段树啥的维护,然后不会了……

多么不幸,我竟然没有发现,当 a i ⩾ 1 a_i\geqslant 1 ai1 时,最多破除 log ⁡ T \log T logT 个诅咒!因为总花费时间至少翻倍。果然旧神 F i r e W i n g B i r d \sf FireWingBird FireWingBird 是令人望而生畏的。

所以 O ( n log ⁡ T ) \mathcal O(n\log T) O(nlogT) 求出 a i ⩾ 1 a_i\geqslant 1 ai1 的答案之后,直接求出剩下的时间可以选多少个 a i = 0 a_i=0 ai=0 的。总时间复杂度 O ( n log ⁡ T + n log ⁡ n ) \mathcal O(n\log T+n\log n) O(nlogT+nlogn)

代码

题外话:这么短的代码,竟然因为处理 a i = 0 a_i=0 ai=0 的封印时没有 + 1 +1 +1 而调试了很久……

#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		(c == '-') ? (f = -f) : 0;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MAXN = 200005;
const int LOGN = 35;
int dp[LOGN];

std::pair<int,int> a[MAXN];
bool cmp(const pair<int,int> &x,const pair<int,int> &y){
	if(x.first == 0 and y.first == 0)
		return x.second < y.second; // sort by b
	if(y.first == 0) return true; // he's infinity
	if(x.first == 0) return false; // I am infinity
	return x.second < (int_(y.second+1)*x.first-1)/y.first;
}
int main(){
	int n = readint(), T = readint();
	rep(i,1,n) a[i].first = readint(), a[i].second = readint();
	sort(a+1,a+n+1,cmp); // sort greedily
	int mid = 1; while(mid <= n && a[mid].first) ++ mid;
	rep(i,1,LOGN-1) dp[i] = T+1; // invalid
	rep(i,1,mid-1) drep(j,LOGN-2,0)
		if(dp[j]+1 <= dp[j+1]/(a[i].first+1))
			dp[j+1] = min(dp[j+1],(dp[j]+1)*(a[i].first+1)+a[i].second);
	int ans = 0, sum = 0;
	for(int i=LOGN-1,j=mid; i>=0; --i){
		if(dp[i] > T) continue; // too big
		while(j <= n && sum+a[j].second+1 <= T-dp[i])
			sum += a[j].second+1, ++ j;
		ans = max(ans,i+j-mid); // [mid,j) is acceptable
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值