个人难度:Medium-
题目大意
有
n
n
n 个怪物,每个怪物有伤害值
a
a
a 和类型
b
b
b ,一共有
m
m
m 种类型。你有
m
m
m 个护身符,携带第
i
i
i 个护身符可以抵挡类型为
i
i
i 的怪物的伤害。你初始有血量
h
h
h,血量降到0及以下就会死。求对于每个整数
K
=
0
,
1
,
…
,
M
K=0,1,\dots,M
K=0,1,…,M,求出携带恰好
K
K
K 个护身符时,在不死亡的前提下能击败的最多怪物数量.询问之间互相独立。
1
≤
m
≤
n
≤
3
∗
1
0
5
,
1
≤
a
≤
1
0
9
,
1
≤
h
≤
1
0
9
1≤m≤n≤3*10^5,1≤a≤10^9,1≤h≤10^9
1≤m≤n≤3∗105,1≤a≤109,1≤h≤109
题解
发现答案单调,且直接求是较为困难的,故考虑另一个问题:对于每个整数
X
=
0
,
1
,
…
,
N
X=0,1,\dots,N
X=0,1,…,N,求出至少需要携带多少个护身符才能至少击败前
X
X
X 个怪物。
第
i
i
i 个护身符能抵挡的伤害实际上就是所有类型为
i
i
i 的怪物的伤害总和。也可以抽象理解为,初始选择了所有护身符,现在要丢弃一些护身符,丢弃第
i
i
i 个护身符所造成的伤害是所有类型为
i
i
i 的怪物的伤害总和。现在要丢弃数量尽可能多的护身符,使得造成的伤害小于
h
h
h 。
容易想到的一个暴力是每加进一个怪物,就把此时每个护身符的伤害进行排序,复杂度为
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)。
考虑优化这个暴力,由于瓶颈在于排序,我一开始想到的是桶排,但是这样加上离散化仍然是
O
(
n
2
)
O(n^2)
O(n2) 的,然后想到二分。于是写一棵权值线段树,维护这个区间内的护身符数量和造成的伤害总和,支持单点修改。最后在线段树上二分即可。
由于权值过大,需要写动态开点线段树或离散化。
Code
#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N=3e5+5,INF=2e9;
typedef long long ll;
int n,m,k,num,ans[N],a[N],b[N];
ll c[N],s[N];
struct segment_tree{
ll sum[N<<2];
int cnt[N<<2];
void pushup(int p){
cnt[p]=cnt[ls]+cnt[rs];
sum[p]=sum[ls]+sum[rs];
}
void update(int p,int l,int r,int a,int b){
if(l==r){
cnt[p]+=b;
sum[p]+=b*c[l];
return;
}
int mid=l+r>>1;
if(a<=mid) update(ls,l,mid,a,b);
else update(rs,mid+1,r,a,b);
pushup(p);
}
int query(int p,int l,int r,ll a){
if(l==r){
if(c[l]==0) return cnt[p];
else return a/c[l]+(a%c[l]!=0);
}
int mid=l+r>>1;
if(sum[ls]>=a) return query(ls,l,mid,a);
else return cnt[ls]+query(rs,mid+1,r,a-sum[ls]);
}
}t;
int main(){
scanf("%d%d%d",&n,&m,&k);
c[++num]=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
s[b[i]]+=a[i];
c[++num]=s[b[i]];
}
c[++num]=INF;
sort(c+1,c+1+num);
int sz=unique(c+1,c+1+num)-c-1;
memset(s,0,sizeof(s));
t.update(1,1,sz,1,m);
t.update(1,1,sz,sz,1);
for(int i=1,x;i<=n;i++){
x=lower_bound(c+1,c+1+sz,s[b[i]])-c;
t.update(1,1,sz,x,-1);
s[b[i]]+=a[i];
x=lower_bound(c+1,c+1+sz,s[b[i]])-c;
t.update(1,1,sz,x,1);
ans[i]=m-t.query(1,1,sz,k);
ans[i]=min(ans[i]+1,m);
}
int pos=1;
for(int i=0;i<=m;i++){
while(ans[pos]<=i&&pos<=n) pos++;
printf("%d ",pos-1);
}
return 0;
}