首页 > 其他分享 >洛谷 P3261 [JLOI2015] 城池攻占 题解

洛谷 P3261 [JLOI2015] 城池攻占 题解

时间:2024-03-14 18:56:27浏览次数:26  
标签:P3261 JLOI2015 idx int 题解 tree pragma now root

题目分析

其他人要么倍增,要么左偏树,那我就来讲讲朴实无华的 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;
}

总结 & 后话

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

标签:P3261,JLOI2015,idx,int,题解,tree,pragma,now,root
From: https://www.cnblogs.com/XuYueming/p/18073698

相关文章

  • 「PKUSC2022」雀圣 题解
    这边电脑的输入法要把我干烧了。。D2T3出这种模拟题,那简直不要太好。首先,不难想到暴力搜索要摸那些牌,然后丢哪些牌。然后手搓一些\(\texttt{check}\),这个应该都会,但是实际上比正解难写。我不知道\(\texttt{lay}\)打的什么东西,比我快20多倍,但是个人认为我这个思路挺暴力的。......
  • P1829 / SP8099 Crash的数字表格 题解
    P1829/SP8099Crash的数字表格题解莫比乌斯反演、数论分块、杜教筛。题目传送门题意简述求以下式子的值,对\(20101009\)(一个质数)取模:\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m\operatorname{lcm}(i,j)\]\(n,m\le10^7\)。加强:\(n,m\le10^{10}\)。解法莫比乌斯反......
  • 2024-03 STEMA 考试C++ 中级真题解析
    2024-03-10STEMA考试C++中级真题解析一、选择题(50分)1、    (110010)2+(c3)16的结果是(B )。A.(240)10        B.(11110101)2        C.(366)8        D.(f6)16备注:此题目下标代表进制,因不支持md格式。  2、   表达式1000/3的结果是(......
  • 服务平滑迁移:eureka迁移到nacos。无法注册双中心的问题解决
    迁移的文档:https://www.alibabacloud.com/help/zh/edas/developer-reference/smoothly-migrate-a-spring-cloud-cluster-that-contains-multiple-applications-to-edas其中遇到的问题未配置排除配置项时(exclude={RibbonEurekaAutoConfiguration.class}),ribbonServerList不是......
  • Codeforces Round 933 (Div. 3) A - G 题解
    CodeforcesRound933(Div.3)A-RudolfandtheTicketSolution因为\(n\)很小,直接枚举\(b_i+c_j\)所产生的所有数字Code#include<bits/stdc++.h>usingnamespacestd;voidsolve(){intn,m,k;cin>>n>>m>>k;intans=0;......
  • thinkphp 5 跨域问题解决
    版本:5.1.41LTS从网上搜到好多从/public/index.php添加heade信息,或者用中间件,或者添加behavior操作,可以做到解决跨域问题,但是亲身试验了都不行,今天刚找了一个,可以使用,放在这里header('Access-Control-Allow-Credentials:true');header('Access-Control-Allow-Methods:GET,......
  • 常见问题解决 --- vmware地址分配失败
    vmware是根据分配给客户机的ip决定它处于什么网路。这句话非常抽象,我举例说明,vmware默认有三张网卡,一个桥接网卡,一个nat网卡,一个仅主机。我先说第一中情况 如果里配置客户机是桥接网卡,且在配置器中选择自动桥接。如果里宿主机有一张网卡,那么就桥接那一张网卡。并获取网路内的d......
  • Edu 12 --- Simple Subset -- 题解 (一个比较巧妙的思维算法题)
    SimpleSubset:题解:  思路解析:    题目要求任意两个数的和为质数,那我们最坏情况就是任意选择一个数,此时子集为最大。    如果子集中有两个奇数或者偶数,他们两个之和一定会被2整除,那么我们只能选择一奇一偶。    如果多个奇数都为1的话,他们两两......
  • 【NOIP2013模拟联考8】匹配(match) 题解
    B组都说看不懂……我也解释不清啊……只能写这么详细了ac自动机ac自动机上dp怎么才能判定一个母串是否包含几个模式串?我们可以想到ac自动机,考虑对模式串建ac自动机,如果我们跑到了一个标记为tail的节点,说明我们的母串包含了这一个模式串。所以我们设\(f[i][s][......
  • LeetCode[题解] 2864. 最大二进制奇数
    题目给你一个二进制字符串s,其中至少包含一个'1'。你必须按某种方式重新排列字符串中的位,使得到的二进制数字是可以由该组合生成的最大二进制奇数。以字符串形式,表示并返回可以由给定组合生成的最大二进制奇数。注意返回的结果字符串可以含前导零。示例1:输入:s......