题目
题目背景
在设好魔法阵之后,漆黑的套子突然膨胀,一闭眼、一睁眼,便已进入了一个神秘的空间。四下漆黑一片,唯见脚下有两排冒着鬼火般蓝色荧光的细线,框出了一条路。
别无选择,只能沿着这条路向前走。走到末了,前有一个两人高的石碑,上面用索莫兰语写着两句话。这是最古老的语言,早在人类直立行走之前,就能在太阳居屋里听到。翻译过来,第一句话是:有的人死了,他还活着
第二句话是:你们都不是什么好东西
慢慢将手靠近石碑……忽而碰到了什么,不可见的实体,挡住了手。
“原来太阳的震颤,引来了旧神 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
n⩽105 但
T
⩽
1
0
9
T\leqslant 10^9
T⩽109 。保证
0
⩽
a
i
,
b
i
⩽
1
0
9
0\leqslant a_i,b_i\leqslant 10^9
0⩽ai,bi⩽109 。
思路
无论是什么做法,首先需要给封印排序。简单推导可知,排序依据是
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 ai⩾1 时,最多破除 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 ai⩾1 的答案之后,直接求出剩下的时间可以选多少个 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;
}