国庆快乐!!!(你们CSP进了吗)

十月的风吹过金色的国庆

 希望你收获满满的笑容与温暖,

假期的每一刻,都值得被珍惜。

祝老友,假期愉快🍂

进入正题吧!

浅谈重链剖分

介绍

重链剖分就是一些善(du)良(liu)出题人为了恶心我们加强难度,把序列上的问题转到树上的解决方案。具体方法就是把树分成一个个链,通过进行对链的修改实现对整棵树的修改。

步骤

DFS1

第一次深度优先搜索维护出每个节点的深度父亲,子树大小,还有你的重链要往哪一个儿子走(简称重儿子)。

因为我们要让划分出的重链数量尽可能的少,所以说我们要挑子树最大的那个儿子作为重儿子。

这样的话树上的每一条路径都能拆成不超过 logn 条重链。

因为如果向下经过轻边的话,所在子树大小至少会除以二。

DFS2

要想在把树划分成重链,显然不止这些。

因为要对重链进行修改,所以说一条重链的节点编号必须是连续的。所以说要维护每个节点所在重链的顶端是谁(或者说是它属于哪一条重链),以及这个节点的新编号。

最后根据编号将它维护到树形数据结构上。

子树修改/查询

最简单的一集。

注意到子树内的编号都是连续的,所以直接对 [x,x+sizex​−1] 进行操作。

做完了。

链上修改/查询

类似一个求 LCA 的过程。

当两个点不在同一条重链上的时候,就跳那个重链顶端深度更小的结点,对所在的重链进行修改。

跳到同一条重链直接做就行了。

时间复杂度显然是 O(qlog2n) 的

例题

P3384

P3384 【模板】重链剖分/树链剖分 - 洛谷

模板题,没啥好说的,按照上面的直接做就行了

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x&-x)
const int N = 1e5 + 10;
int n, T, r, P, cnt, son[N], depth[N], fa[N], sz[N], id[N], top[N], w[N], c1[N], c2[N];
vector<int> g[N];
//---------这里是数据结构部分-------------
inline void add(int l, int r, int x) {
	int ad1 = (l - 1) * x % P, ad2 = r *x % P;
	for (int i = l; i <= n; i += lowbit(i)) c1[i] = (c1[i] + x) % P, c2[i] = (c2[i] + ad1) % P;
	for (int i = r + 1; i <= n; i += lowbit(i)) c1[i] = (c1[i] - x + P) % P, c2[i] = (c2[i] - ad2 + P) % P;
}
inline int query(int x) {
	int ret = 0;
	for (int i = x; i; i -= lowbit(i)) ret = (ret + c1[i] * x % P) % P, ret = (ret - c2[i] + P) % P;
	return ret;
}
inline int ask(int l, int r) {
	return (query(r) - query(l - 1) + P) % P;
}
//---------这里是树剖部分----------
//dfs基本上都是千篇一律,直接背就行
inline void dfs1(int f, int u) {
	fa[u] = f, sz[u] = 1, depth[u] = depth[f] + 1;
	int tmp = -1;
	for (int v : g[u]) {
		if (v == f) continue;
		dfs1(u, v);
		sz[u] += sz[v];
		if (sz[v] > tmp) tmp = sz[v], son[u] = v;
	}
}
inline void dfs2(int f, int u) {
	top[u] = f, id[u] = ++cnt;
	add(id[u], id[u], w[u]);
	if (son[u] == 0) return;
	dfs2(f, son[u]);
	for (int v : g[u]) {
		if (v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}
//这里是路径,不同的题可能略有修改
inline void addPath(int u, int v, int k) {
	k %= P;
	while (top[u] != top[v]) {
		if (depth[top[u]] < depth[top[v]]) swap(u, v); //LCA 显然是优先跳小的
		add(id[top[u]], id[u], k);
		u = fa[top[u]];
	}
	if (depth[u] > depth[v]) swap(u, v); //深度大的在后
	add(id[u], id[v], k);
}
//和addPath一样就不讲了
inline int askPath(int u, int v) {
	int res = 0;
	while (top[u] != top[v]) {
		if (depth[top[u]] < depth[top[v]]) swap(u, v);
		res = (res + ask(id[top[u]], id[u])) % P, u = fa[top[u]];
	}
	if (depth[u] > depth[v]) swap(u, v);
	res = (res + ask(id[u], id[v])) % P;
	return res;
}
//子树没啥好讲的
inline int askSon(int u) {
	return ask(id[u], id[u] + sz[u] - 1);
}
inline void addSon(int u, int k) {
	k %= P;
	add(id[u], id[u] + sz[u] - 1, k);
}
//---------这里是主函数部分----------
signed main() {
	cin >> n >> T >> r >> P;
	for (int i = 1; i <= n; i++) cin >> w[i];
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs1(0, r), dfs2(r, r);
	int op, u, v, k;
	while (T--) {
		cin >> op >> u;
		if (op == 1) {
			cin >> v >> k;
			addPath(u, v, k);
		} else if (op == 2) {
			cin >> v;
			cout << askPath(u, v) << endl;
		} else if (op == 3) {
			cin >> k;
			addSon(u, k);
		} else {
			cout << askSon(u) << endl;
		}
	}
	return 0;
}

P1505

#include <bits/stdc++.h>
using namespace std;
#define debug cerr<<"The code runs successfully.\n";
#define endl '\n'
#define TRACE 1
#define tcout TRACE && cout
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define int long long
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
const int P = 998244353;
const int Base = 33331;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10, M = 2e6 + 10;

struct segtree {
	int l, r, sum, max, min, lazy;
} s[N << 2];
pair<int, int> edge[N];
vector<pair<int, int>> g[N];
int n, q, id[N], cnt, son[N], siz[N], fa[N], w[N], top[N], dep[N], wssb[N];

inline void calc(int p) {
	s[p].lazy ^= 1;
	s[p].sum = -s[p].sum;
	s[p].max = -s[p].max;
	s[p].min = -s[p].min;
	swap(s[p].max, s[p].min);
}
inline void pushup(int p) {
	s[p].sum = s[lson(p)].sum + s[rson(p)].sum;
	s[p].min = min(s[lson(p)].min, s[rson(p)].min);
	s[p].max = max(s[lson(p)].max, s[rson(p)].max);
}
inline void pushdown(int p) {
	if (!s[p].lazy) return;
	calc(lson(p)), calc(rson(p));
	s[p].lazy ^= 1;
}
inline void build(int p, int l, int r) {
	s[p].l = l, s[p].r = r;
	if (l == r) {
		s[p].sum = s[p].min = s[p].max = w[l];
		return;
	}
	int mid = l + r >> 1;
	build(lson(p), l, mid);
	build(rson(p), mid + 1, r);
	pushup(p);
}
inline void change1(int p, int x, int k) {
	if (s[p].l == s[p].r) {
		s[p].sum = s[p].max = s[p].min = k;
		return;
	}
	pushdown(p);
	int mid = s[p].l + s[p].r >> 1;
	if (mid >= x) change1(lson(p), x, k);
	else change1(rson(p), x, k);
	pushup(p);
}
inline void change2(int p, int l, int r) {
	if (s[p].r < l || r < s[p].l) return;
	if (s[p].l >= l && s[p].r <= r) {
		calc(p);
		return;
	}
	pushdown(p);
	int mid = s[p].l + s[p].r >> 1;
	change2(lson(p), l, r), change2(rson(p), l, r);
	pushup(p);
}
inline int asksum(int p, int l, int r) {
	if (s[p].r < l || r < s[p].l) return 0;
	if (s[p].l >= l && s[p].r <= r) return s[p].sum;
	pushdown(p);
	return asksum(lson(p), l, r) + asksum(rson(p), l, r);
}
inline int askmax(int p, int l, int r) {
	if (s[p].r < l || r < s[p].l) return -INF;
	if (s[p].l >= l && s[p].r <= r) return s[p].max;
	pushdown(p);
	return max(askmax(lson(p), l, r), askmax(rson(p), l, r));
}
inline int askmin(int p, int l, int r) {
	if (s[p].r < l || r < s[p].l) return INF;
	if (s[p].l >= l && s[p].r <= r) return s[p].min;
	pushdown(p);
	return min(askmin(lson(p), l, r), askmin(rson(p), l, r));
}

inline void dfs1(int f, int u) {
	fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
	int tmp = -1;
	for (auto e : g[u]) {
		int v = e.first;
		if (v == f) continue;
		wssb[v] = e.second;
		dfs1(u, v);
		siz[u] += siz[v];
		if (tmp < siz[v]) {
			son[u] = v, tmp = siz[v];
		}
	}
}
inline void dfs2(int f, int u) {
	id[u] = ++cnt, w[cnt] = wssb[u], top[u] = f;
	if (son[u]) dfs2(f, son[u]);
	for (auto e : g[u]) {
		int v = e.first;
		if (fa[u] == v || v == son[u]) continue;
		dfs2(v, v);
	}
}
inline void update1(int x, int k) {
	int ccf;
	if (dep[edge[x].first] > dep[edge[x].second]) ccf = edge[x].first;
	else ccf = edge[x].second;
	change1(1, id[ccf], k);
}
inline void update2(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		change2(1, id[top[x]], id[x]);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	if (x != y) change2(1, id[x] + 1, id[y]); //就是这里需要注意一下
}
inline int querysum(int x, int y) {
	int res = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		res += asksum(1, id[top[x]], id[x]);
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	if (x != y) res += asksum(1, id[x] + 1, id[y]); //就是这里需要注意一下
	return res;
}
inline int querymax(int x, int y) {
	int res = -INF;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		res = max(res, askmax(1, id[top[x]], id[x]));
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	if (x != y) res = max(res, askmax(1, id[x] + 1, id[y]));
	return res;
}
inline int querymin(int x, int y) {
	int res = INF;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		res = min(res, askmin(1, id[top[x]], id[x]));
		x = fa[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	if (x != y) res = min(res, askmin(1, id[x] + 1, id[y]));
	return res;
}

signed main() {
	fst;
	cin >> n;
	for (int i = 1; i < n; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		u++, v++;
		g[u].push_back({v, w});
		g[v].push_back({u, w});
		edge[i] = {u, v};
	}
	dfs1(0, 1);
	dfs2(1, 1);
	build(1, 1, n);
	cin >> q;
	while (q--) {
		string op;
		int x, y;
		cin >> op >> x >> y;
		if (op == "C") {
			update1(x, y);
		} else if (op == "N") {
			x++, y++;
			update2(x, y);
		} else if (op == "SUM") {
			x++, y++;
			cout << querysum(x, y) << endl;
		} else if (op == "MAX") {
			x++, y++;
			cout << querymax(x, y) << endl;
		} else {
			x++, y++;
			cout << querymin(x, y) << endl;
		}
	}
	return 0;
}

P1505 [国家集训队] 旅游 - 洛谷

也是几乎板子,线段树稍微改一改。

但是这里有一个 trick 就是边权转点权。

对于每一条边,把它的边权转移到通向的深度更大的点。

这里有一个细节,进行链上修改的时候,跳到了同一条重链,这个时候深度更小的点的点权是不会进行修改的,然后就可以直接做了。

<think>在C++编程中,特别是在竞赛(如CSP、NOIP等)中,经常使用万能头文件`bits/stdc++.h`来包含所有标准库头文件。这样可以避免逐个包含头文件的麻烦。但是,是否能在CSP竞赛中使用万能头文件呢? 回答: 在CSP认证考试中,可以使用万能头文件`bits/stdc++.h`。原因如下: 1. **CSP竞赛环境**:CSP认证考试通常使用较新版本的GCC编译器(如GCC 7.5.0或更高),这些编译器支持万能头文件。 2. **标准库支持**:万能头文件是GCC编译器特有的,而CSP竞赛环境通常使用GCC,因此支持该头文件。 3. **实际使用**:历年参赛者广泛使用万能头文件,没有报告因此导致编译错误。 但需要注意: - 万能头文件会显著增加编译时间,但对运行时间无影响。 - 在某些非GCC编译器(如MSVC)中可能不支持,但CSP考试环境为Linux+GCC,故无此问题。 - 万能头文件并非C++标准,但在竞赛环境中被允许。 因此,在CSP竞赛中可以安心使用万能头文件。 §§相关问题§§: 1. 万能头文件`bits/stdc++.h`包含哪些内容? 2. 使用万能头文件有哪些优缺点? 3. 在哪些编程竞赛中不推荐使用万能头文件? 4. 如何在没有万能头文件的环境下快速包含常用头文件? </think>### 回答:万能头文件在CSP竞赛中的使用 在CSP竞赛(中国计算机学会软件能力认证)中,**可以使用万能头文件`#include <bits/stdc++.h>`**。原因如下: 1. **支持标准**: - CSP竞赛环境使用GCC编译器(通常是Linux环境) - GCC编译器支持`bits/stdc++.h`头文件 - 官方说明文档中未禁止使用该头文件 2. **实际验证**: - 历年CSP认证考试中,参赛者广泛使用该头文件 - 官方评测系统能够正常编译含该头文件的代码 3. **使用建议**: ```cpp #include <bits/stdc++.h> // 推荐在CSP中使用 using namespace std; int main() { // 竞赛代码 return 0; } ``` 4. **注意事项**: - 仅在GCC编译环境有效(如Clang/MSVC不支持) - 编译时间增加(约0.5~1秒),但对运行时间无影响 - 部分企业级开发禁用(但竞赛中无此限制)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值