题解:P11217 【MX-S4-T1】「yyOI R2」youyou 的垃圾桶
P11217题解
Part 0 致谢:
感谢 mengzdd 大佬对该题解提出的宝贵建议。
Part 1 读题 & 分析
该题解做法:线段树 + 二分。
题目大意:
youyou 要去打仗,打仗前有一部分敌人的战斗力会上升,接着,youyou 会一个接着一个打过去,直到 Ta 死了,当 youyou 战胜了一个敌人之后(剩余血量无法被那个敌人一击致命),那个敌人便会重新“满血复活”,并且战斗力提高至原来的
看到这里,我们可以发现,要实现两种操作,区间修改和区间查询,所以我们立刻想到了线段树。
Part 2 修改
题目要求在战斗开始前要修改一部分连续垃圾桶的战斗力,这是一个线段树十分典型的运用(就是板子),不会的请出门左转:【模板】线段树1 。
Part 3 计算评分
我们可以发现,youyou 打敌人时,肯定是先打了
int level = 1;
int ans = 0;
int blood = w;
int all = query(1,1,n,1,n); // 统计战斗力之和,query函数见下面
while(1){
if (blood <= all) break; // 如果剩余血量不够杀一整排敌人了,退出循环
blood -= all; // 扣血
all *= 2; // 因为敌人的血量会翻倍,所以要乘 2
ans += n; // 又战胜了 n 个敌人
level *= 2;
}
接下来我们要统计,youyou 还能杀多少个敌人,于是我们立刻就想到了二分。
但是我们会发现,我们并没有修改战斗力,因为所有敌人的战斗力都乘上了 level *= 2;
二分代码如下:
int L = 1,R = n,tmid,p = 0;
while(L <= R){
tmid = (L + R) >> 1;
if (query(1,1,n,1,tmid) * level < blood){
p = tmid;
L = tmid + 1;
}else{
R = tmid - 1;
}
}
Part 4 优化
我们会发现,计算评分的时间复杂度是
二分放在外面是十分浪费时间的,我们可以直接对线段树进行二分,如果左儿子的权值大于所求权值,则递归右子树,否则递归左子树,做法显然是正确的。
但是,还是有一点坑的,比如这个。
你不能写成这样:
if (blo >= sum[o << 1] * lev) return query2(o << 1 | 1,mid + 1,r,lev,blo - sum[o << 1] * lev);
而应该写成这样:
if (blo > sum[o << 1] * lev) return query2(o << 1 | 1,mid + 1,r,lev,blo - sum[o << 1] * lev);
这是因为,最后一个不算,如果一个人的血量刚好能被那个敌人击败,那么是不算的,所以不能写成大于等于号。
最后的答案显然是
闲话:如果你写成了 blo >= sum[o << 1] * lev
,你过不了第一个样例,但是可以 AC,所以本题 hack 数据就是第一个样例(赛时)。
本部分代码:
int query2(int o,int l,int r,int lev,int blo){
if (l == r) return l; // 返回最多能打到的编号
int mid = (l + r) >> 1;
pushdown(o,l,r);
if (blo > sum[o << 1] * lev) return query2(o << 1 | 1,mid + 1,r,lev,blo - sum[o << 1] * lev); // 左儿子的权值大于所求权值,递归右子树,不要忘记血量要减少
else return query2(o << 1,l,mid,lev,blo); // 递归左子树
}
Part 5 AC Code
时间复杂度是
线段树空间开
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int n,q,w;
int a[N],sum[N << 2],lzy[N << 2];
void pushup(int o){
sum[o] = sum[o << 1] + sum[o << 1 | 1];
}
void build(int o,int l,int r){
if (l == r){
sum[o] = a[l];
return;
}
int mid = (l + r) >> 1;
build(o << 1,l,mid);
build(o << 1 | 1,mid + 1,r);
pushup(o);
}
void pushdown(int o,int l,int r){
int mid = (l + r) >> 1;
sum[o << 1] += lzy[o] * (mid - l + 1);
sum[o << 1 | 1] += lzy[o] * (r - mid);
lzy[o << 1] += lzy[o];
lzy[o << 1 | 1] += lzy[o];
lzy[o] = 0;
}
void update(int o,int l,int r,int ql,int qr,int x){
if (ql <= l && r <= qr){
sum[o] += (r - l + 1) * x;
lzy[o] += x;
return;
}
int mid = (l + r) >> 1;
pushdown(o,l,r);
if (ql <= mid) update(o << 1,l,mid,ql,qr,x);
if (qr > mid) update(o << 1 | 1,mid + 1,r,ql,qr,x);
pushup(o);
}
int query(int o,int l,int r,int ql,int qr){
if (ql <= l && r <= qr)
return sum[o];
int mid = (l + r) >> 1;
int ret = 0;
pushdown(o,l,r);
if (ql <= mid) ret += query(o << 1,l,mid,ql,qr);
if (qr > mid) ret += query(o << 1 | 1,mid + 1,r,ql,qr);
return ret;
}
int query2(int o,int l,int r,int lev,int blo){
if (l == r) return l;
int mid = (l + r) >> 1;
pushdown(o,l,r);
if (blo > sum[o << 1] * lev) return query2(o << 1 | 1,mid + 1,r,lev,blo - sum[o << 1] * lev);
else return query2(o << 1,l,mid,lev,blo);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q >> w;
for (int i = 1 ; i <= n ; i++) cin >> a[i];
build(1,1,n);
for (int i = 1 ; i <= q ; i++){
int l,r,d;
cin >> l >> r >> d;
update(1,1,n,l,r,d);
int level = 1;
int ans = 0;
int blood = w;
int all = query(1,1,n,1,n);
while(1){
if (blood <= all) break;
blood -= all;
all *= 2;
ans += n;
level *= 2;
}
int p = query2(1,1,n,level,blood);
cout << ans + p - 1 << '\n';
}
return 0;
}