前言

题目链接:洛谷

题目分析

其他人要么倍增,要么左偏树,那我就来讲讲朴实无华的 dfs 序加上线段树的做法。

首先发现题目中明确指出了作乘法的时候一定是乘上一个大于零的数,这是为什么呢?首先把可以占领当前城池的战斗力的不等式列出来:

\[h_j \le \left\{
\begin{array}{c}
s_i \times v_j & & {a_j = 1}\\
s_i + v_j & & {a_j=0}
\end{array}
\right.
\]

发现当 \(v_j > 0\) 时,不等式不会变号,得到如下式子:

\[s_i \ge \left\{
\begin{array}{c}
\cfrac{h_j}{v_j} & & {a_j = 1}\\
h_j - v_j & & {a_j=0}
\end{array}
\right.
\]

于是,我们发现,判断一大堆骑士能不能占领只用看其中的最小血量是否满足要求就行了。但是代码实现的时候为了避免丢失精度,不使用浮点数比较。对于当前城池,我们要知道起子树内所有能跳到这里来的骑士的最小血量,删去牺牲在这里的骑士,将留下来的骑士血量按照题意操作,在往上跳一步。子树的问题可以用 dfs 序转变成一个区间上的问题,区间最小值、区间乘、区间加、单点删除,明显可以用线段树维护。删除的时候将其战斗力设为 \(\infty\) 就不会对之后的造成影响。时间复杂度 \(\Theta(n \log n)\),令 \(n\) 和 \(m\) 同阶。

实际代码实现起来码量很大?细节需要处理到位,特别是这题 dfs 序记的不是结点,而是士兵,所以会略有不同,具体见代码。

代码(已略去快读快写,码风清新,注释详尽)

dfs 序

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std; #include <vector> const int N = 300010;
const long long inf = 0x3f3f3f3f3f3f3f3fll; int n, m;
vector<int> edge[N], man[N];
int ans1[N], ans2[N];
// 对于 ans2,转变成初始位置深度 - 死亡位置深度,根节点深度 1,没死的当做在深度为 0 的地方死了 int op[N], dpt[N];
long long h[N], v[N], s[N]; int L[N], R[N], val[N], timer; void dfs(int now){
L[now] = timer + 1;
for (auto x: man[now]) val[++timer] = x;
for (auto to: edge[now]) dfs(to);
R[now] = timer;
} // dfs 序记录子树所有士兵 struct Segment_Tree{
#define lson (idx << 1 )
#define rson (idx << 1 | 1) struct Tag{
long long mul, add;
Tag operator + (const Tag & o) const {
return {mul * o.mul, add * o.mul + o.add};
}
inline void clear(){
mul = 1, add = 0;
}
};
// 懒惰标记 struct Info{
long long minn;
int pos;
Info operator + (const Info & o) const {
if (minn == inf) return o;
if (o.minn == inf) return *this;
if (minn < o.minn) return *this;
return o;
}
Info operator + (const Tag & o) const {
if (minn == inf) return *this;
return {minn * o.mul + o.add, pos};
}
};
// 信息 struct node{
int l, r;
Info info;
Tag tag;
} tree[N << 2]; void pushup(int idx){
tree[idx].info = tree[lson].info + tree[rson].info;
} void build(int idx, int l, int r){
tree[idx] = {l, r, inf, -1, 1, 0};
if (l == r) return tree[idx].info = {s[val[l]], l}, void();
int mid = (l + r) >> 1;
build(lson, l, mid), build(rson, mid + 1, r), pushup(idx);
} void pushtag(int idx, const Tag t){
tree[idx].info = tree[idx].info + t;
tree[idx].tag = tree[idx].tag + t;
} void pushdown(int idx){
pushtag(lson, tree[idx].tag), pushtag(rson, tree[idx].tag);
tree[idx].tag.clear();
} Info query(int idx, int l, int r){
if (tree[idx].l > r || tree[idx].r < l) return {inf, -1};
if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].info;
return pushdown(idx), query(lson, l, r) + query(rson, l, r);
} void modify(int idx, int l, int r, const Tag t){
if (tree[idx].l > r || tree[idx].r < l) return;
if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, t);
pushdown(idx), modify(lson, l, r, t), modify(rson, l, r, t), pushup(idx);
} void erase(int pos){
modify(1, pos, pos, {0, inf});
} void add(int l, int r, long long v){
modify(1, l, r, {1, v});
} void mul(int l, int r, long long v){
modify(1, l, r, {v, 0});
} void output(int idx){
if (tree[idx].l == tree[idx].r){
cerr << (tree[idx].info.minn == inf ? -1 : tree[idx].info.minn) << " \n"[tree[idx].l == timer];
return;
}
pushdown(idx), output(lson), output(rson);
} #undef lson
#undef rson
} yzh;
// 貌似就是线段树 2 ? void redfs(int now){
if (L[now] > R[now]) return;
for (auto to: edge[now]) redfs(to);
// yzh.output(1);
while (true){
// 不断删去死了的士兵,注意到士兵最多删 m 次,故不会超时
Segment_Tree::Info res = yzh.query(1, L[now], R[now]);
if (res.pos == -1 || res.minn == inf) break;
if (res.minn >= h[now]) break;
ans2[val[res.pos]] -= dpt[now], yzh.erase(res.pos), ++ans1[now];
// yzh.output(1);
}
if (op[now]) yzh.mul(L[now], R[now], v[now]);
else yzh.add(L[now], R[now], v[now]);
}
// 第二次深搜求得答案 signed main(){
dpt[1] = 1, read(n, m);
for (int i = 1; i <= n; ++i) read(h[i]);
for (int i = 2, fa; i <= n; ++i) read(fa, op[i], v[i]), edge[fa].push_back(i), dpt[i] = dpt[fa] + 1;
for (int i = 1, pos; i <= m; ++i) read(s[i], pos), man[pos].push_back(i), ans2[i] = dpt[pos];
dfs(1), yzh.build(1, 1, timer), redfs(1);
for (int i = 1; i <= n; ++i) write(ans1[i], '\n');
for (int i = 1; i <= m; ++i) write(ans2[i], '\n');
return 0;
}

倍增(虽然没讲,但是也给出吧?)

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std; int n, m;
int op[300010]; int ans1[300010], ans2[300010]; int yzh[300010][20];
long long add[300010][20], mul[300010][20];
long long L[300010][20]; signed main(){
read(n, m);
for (int i = 1; i <= n; ++i) read(L[i][0]);
for (int i = 2, op; i <= n; ++i){
read(yzh[i][0], op), read(op ? mul[i][0] : (mul[i][0] = 1, add[i][0]));
}
for (int k = 1; k <= 19; ++k)
for (int i = 1; i <= n; ++i) if (!!(yzh[i][k] = yzh[yzh[i][k - 1]][k - 1])){
mul[i][k] = mul[i][k - 1] * mul[yzh[i][k - 1]][k - 1];
add[i][k] = add[i][k - 1] * mul[yzh[i][k - 1]][k - 1] + add[yzh[i][k - 1]][k - 1];
L[i][k] = max(L[i][k - 1], (L[yzh[i][k - 1]][k - 1] - add[i][k - 1] - 1) / mul[i][k - 1] + 1);
}
for (int i = 1, now; i <= m; ++i){
long long val; read(val, now);
for (int j = 19; j >= 0; --j)
if (yzh[now][j] && L[now][j] <= val)
ans2[i] += 1 << j, val = val * mul[now][j] + add[now][j], now = yzh[now][j];
if (val >= L[now][0]) ++ans2[i];
else ++ans1[now];
}
for (int i = 1; i <= n; ++i) write(ans1[i], '\n');
for (int i = 1; i <= m; ++i) write(ans2[i], '\n');
return 0;
}

左偏树

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std; int n, m;
typedef int array[300010];
typedef long long Array[300010];
array lson, rson, root, a, dpt, fa, ans1, ans2, dis;
Array add, mul, h, s, v; inline void pushtag(int x, long long mul, long long add){
::add[x] = ::add[x] * mul + add, ::mul[x] *= mul;
s[x] = s[x] * mul + add;
} inline void pushdown(int x){
if (lson[x]) pushtag(lson[x], mul[x], add[x]);
if (rson[x]) pushtag(rson[x], mul[x], add[x]);
add[x] = 0, mul[x] = 1;
} int merge(int x, int y){
if (!x || !y) return x | y;
if (s[x] > s[y]) swap(x, y);
pushdown(x), rson[x] = merge(rson[x], y);
if (dis[lson[x]] < dis[rson[x]]) swap(lson[x], rson[x]);
return dis[x] = dis[rson[x]] + 1, x;
} signed main(){
dpt[1] = 1, read(n, m);
for (int i = 1; i <= n; ++i) read(h[i]);
for (int i = 2; i <= n; ++i) read(fa[i], a[i], v[i]), dpt[i] = dpt[fa[i]] + 1, mul[i] = 1;
for (int i = 1, bl; i <= m; ++i) read(s[i], bl), root[bl] = merge(root[bl], i), ans2[i] = dpt[bl];
for (int i = n; i >= 1; --i){
while (root[i] && s[root[i]] < h[i]){
ans2[root[i]] -= dpt[i], pushdown(root[i]), ++ans1[i];
root[i] = merge(lson[root[i]], rson[root[i]]);
}
if (i == 1) break;
if (root[i] == 0) continue;
if (a[i]) pushtag(root[i], v[i], 0);
else pushtag(root[i], 1, v[i]);
pushdown(root[i]), root[fa[i]] = merge(root[fa[i]], root[i]);
}
for (int i = 1; i <= n; ++i) write(ans1[i], '\n');
for (int i = 1; i <= m; ++i) write(ans2[i], '\n');
return 0;
}

总结 & 后话

线段树无敌爱敲,另外两种做法码量小,速度快,虽然线段树码量大,速度慢,但是思路简单是个不错的选择!

[JLOI2015] 城池攻占 题解的更多相关文章

  1. P3261 [JLOI2015]城池攻占 题解

    题目 小铭铭最近获得了一副新的桌游,游戏中需要用 \(m\) 个骑士攻占 \(n\) 个城池.这 \(n\) 个城池用 \(1\) 到 \(n\) 的整数表示.除 \(1\) 号城池外,城池 \(i\ ...

  2. BZOJ4003:[JLOI2015]城池攻占——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4003 https://www.luogu.org/problemnew/show/P3261 小铭 ...

  3. 【BZOJ4003】[JLOI2015]城池攻占 可并堆

    [BZOJ4003][JLOI2015]城池攻占 Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 1 号 ...

  4. BZOJ_4003_[JLOI2015]城池攻占_可并堆

    BZOJ_4003_[JLOI2015]城池攻占_可并堆 Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 ...

  5. [bzoj4003][JLOI2015]城池攻占_左偏树

    城池攻占 bzoj-4003 JLOI-2015 题目大意:一颗n个节点的有根数,m个有初始战斗力的骑士都站在节点上.每一个节点有一个standard,如果这个骑士的战斗力超过了这个门槛,他就会根据城 ...

  6. [洛谷P3261] [JLOI2015]城池攻占(左偏树)

    不得不说,这道题目是真的难,真不愧它的“省选/NOI-”的紫色大火题!!! 花了我晚自习前半节课看题解,写代码,又花了我半节晚自习调代码,真的心态爆炸.基本上改得和题解完全一样了我才过了这道题!真的烦 ...

  7. [JLOI2015]城池攻占 左偏树

    题目描述 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池.这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其中 fi &l ...

  8. BZOJ4003 [JLOI2015]城池攻占 左偏树 可并堆

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ4003 题意概括 题意有点复杂,直接放原题了. 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑 ...

  9. [JLOI2015]城池攻占

    题目描述 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池.这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其中 fi &l ...

  10. BZOJ4003[JLOI2015]城池攻占——可并堆

    题目描述 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖, 其中 fi ...

随机推荐

  1. 这个vue3的后台管理系统虽然简洁但不简单

    今天介绍一个新的Vue后台管理框架,相比其他后台功能丰富管理系统,这个后台管理系统可以用干净简洁来形容--Nova-admin Nova-admin Nova-admin 是一个基于Vue3.Vite ...

  2. scab2

    package com.cmb.cox.utils;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;im ...

  3. JAVA-poi导出excel到http响应流

    导出结果为excel是相对常见的业务需求,大部分情况下只需要导出简单的格式即可,所以有许多可以采用的方案.有些方案还是很容易实现的. 一.可用的解决方案 目前可以有几类解决方案: 字处理企业提供的解决 ...

  4. Java面试知识点(四)重写和重载

    重写override 在 java 中有很多的继承,继承下来的有变量.方法.在有一些子类要实现的方法中,方法名.传的参数.返回值跟父类中的方法一样,但具体实现又跟父类的不一样,这时候我们就需要重写父类 ...

  5. 高通参考设计中MTP与QRD

    高通参考设计中MTP与QRD 背景 之前在调试设备树的时候,看到设备树带了一个qrd的后缀,一直没搞清楚.上网找资料也好像不是我想要的. 今天查阅lk侧的代码,发现了HW_PLATFORM_HRD这个 ...

  6. KES数据库实践指南:探索KES数据库的事务隔离级别

    引言 前两篇文章我们详细讲解了如何安装KES金仓数据库,并提供了快速查询和搭建基于coze平台的智能体的解决方案.今天,我们的焦点将放在并发控制机制和事务隔离级别上. 本文将通过一系列实验操作,深入探 ...

  7. 写给rust初学者的教程(一):枚举、特征、实现、模式匹配

    这系列RUST教程一共三篇.这是第一篇,介绍RUST语言的入门概念,主要有enum\trait\impl\match等语言层面的东西. 安装好你的rust开发环境,用cargo创建一个空项目,咱们直接 ...

  8. yb课堂实战之接口协议调整和日期格式 《十八》

    调整api接口协议和日期格式 统一输出协议,驼峰转下划线 格式化日期

  9. MViT:性能杠杠的多尺度ViT | ICCV 2021

    论文提出了多尺度视觉Transformer模型MViT,将多尺度层级特征的基本概念与Transformer模型联系起来,在逐层扩展特征复杂度同时降低特征的分辨率.在视频识别和图像分类的任务中,MViT ...

  10. oeasy教您玩转linux 010211 牛说 cowsay

    我们来回顾一下 上一部分我们都讲了什么? 软件包工具是 apt 软件包不但能下载,也能升级,还能删除 专门管理软件包的 aptitude 这次我们下载个牛说 cowsay: sudo apt inst ...