P3261 [JLOI2015] 城池攻占 分析
题目概述
小铭铭最近获得了一副新的桌游,游戏中需要用 \(m\) 个骑士攻占 \(n\) 个城池。
这 \(n\) 个城池用 \(1\) 到 \(n\) 的整数表示。除 \(1\) 号城池外,城池 \(i\) 会受到另一座城池 \(f_i\) 的管辖,其中 \(f_i<i\)。也就是说,所有城池构成了一棵有根树。
这 \(m\) 个骑士用 \(1\) 到 \(m\) 的整数表示,其中第 \(i\) 个骑士的初始战斗力为 \(s_i\),第一个攻击的城池为 \(c_i\)。
每个城池有一个防御值 \(h_i\),如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击管辖这座城池的城池,直到占领 \(1\) 号城池,或牺牲为止。
除 \(1\) 号城池外,每个城池 \(i\) 会给出一个战斗力变化参数 \((a_i,v_i)\)。若 \(a_i=0\),攻占城池 \(i\) 以后骑士战斗力会增加 \(v_i\);若 \(a_i=1\),攻占城池 \(i\) 以后,战斗力会乘以 \(v_i\)。
注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果。
现在的问题是,对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。
对于 \(100\%\) 的数据,\(1\le n,m\le 3\times 10^5\),\( -10^{18}\le h_i,v_i,s_i\le 10^{18}\),\(1\le f_i<i,1\le c_i\le n,a_i\in\{0,1\}\),保证 \(a_i=1\) 时,\(v_i>0\),保证任何时候骑士战斗力值的绝对值不超过 \(10^{18}\)。
分析1
对于每个城池,我们维护一个小根堆(或大根堆)来存放所有当前已经到达该城池且尚未牺牲的骑士的战斗力。因为我们只需要快速找出战斗力最小的骑士,看他是否小于防御值,所以用小根堆很自然。
然后考虑到这种“整体加/乘”操作,如果对每个骑士都修改,会超时。我们需要一种支持整体懒惰标记的数据结构,同时还能支持合并两个集合(因为子城池的骑士会向上合并到父城池)。
这就很自然地想到 可并堆(左偏树),并且对每个节点维护懒标记(乘法标记和加法标记),与线段树的懒标记类似。
我写的:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 300000 + 5;
const int MAXM = 300000 + 5;
int n, m;
long long h[MAXN], v[MAXN];
int fa[MAXN], a[MAXN];
int lc[MAXM], rc[MAXM], dist[MAXM];
long long val[MAXM], mul[MAXM], add[MAXM];
int root[MAXN];
int dep[MAXN], dep_start[MAXM];
long long ans_knight[MAXM];
int dead[MAXN];
inline void apply(int x, long long mul_, long long add_) {
if (!x) return;
val[x] = val[x] * mul_ + add_;
mul[x] = mul[x] * mul_;
add[x] = add[x] * mul_ + add_;
}
inline void push(int x) {
if (!x) return;
if (mul[x] != 1 || add[x] != 0) {
apply(lc[x], mul[x], add[x]);
apply(rc[x], mul[x], add[x]);
mul[x] = 1;
add[x] = 0;
}
}
int merge(int a, int b) {
if (!a || !b) return a ? a : b;
if (val[a] > val[b]) swap(a, b);
push(a);
rc[a] = merge(rc[a], b);
if (dist[lc[a]] < dist[rc[a]]) swap(lc[a], rc[a]);
dist[a] = dist[rc[a]] + 1;
return a;
}
int pop(int x) {
push(x);
return merge(lc[x], rc[x]);
}
void dfs(int x) {
if (!x) return;
push(x);
ans_knight[x] = dep_start[x]; // dep[1] = 1, so dep_start - 1 + 1 = dep_start
dfs(lc[x]);
dfs(rc[x]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> h[i];
for (int i = 2; i <= n; ++i) {
cin >> fa[i] >> a[i] >> v[i];
}
dep[1] = 1;
for (int i = 2; i <= n; ++i) dep[i] = dep[fa[i]] + 1;
// 初始化每个骑士对应的左偏树节点
for (int i = 1; i <= m; ++i) {
long long s;
int c;
cin >> s >> c;
val[i] = s;
mul[i] = 1;
add[i] = 0;
lc[i] = rc[i] = 0;
dist[i] = 1;
root[c] = merge(root[c], i);
dep_start[i] = dep[c];
}
// 从叶子向根处理
for (int i = n; i >= 1; --i) {
// 战斗力不足的骑士在城池 i 牺牲
while (root[i] && val[root[i]] < h[i]) {
int x = root[i];
root[i] = pop(root[i]);
dead[i]++;
ans_knight[x] = dep_start[x] - dep[i];
}
// 剩余骑士继续向上,应用城池 i 的变化
if (i != 1 && root[i]) {
if (a[i] == 0)
apply(root[i], 1, v[i]); // 加 v[i]
else
apply(root[i], v[i], 0); // 乘 v[i]
root[fa[i]] = merge(root[fa[i]], root[i]);
}
}
// 最终攻占 1 号城池的骑士
if (root[1]) dfs(root[1]);
for (int i = 1; i <= n; ++i) cout << dead[i] << '\n';
for (int i = 1; i <= m; ++i) cout << ans_knight[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;
}
分析2
由于是子树,不难想到用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;
}
分析3
可以倍增
//#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;
}

浙公网安备 33010602011771号