省队集训,树上
P7737 [NOI2021] 庆典
题目描述
C 国是一个繁荣昌盛的国家,它由 \(n\) 座城市和 \(m\) 条有向道路组成,城市从 \(1\) 到 \(n\) 编号。如果从 \(x\) 号城市出发,经过若干条道路后能到达 \(y\) 号城市,那么我们称 \(x\) 号城市可到达 \(y\) 号城市,记作 \(x\Rightarrow y\)。C 国的道路有一个特点:对于三座城市 \(x\),\(y\),\(z\),若 \(x\Rightarrow z\) 且 \(y\Rightarrow z\),那么有 \(x\Rightarrow y\) 或 \(y\Rightarrow x\)。
再过一个月就是 C 国成立的千年纪念日,所以 C 国的人民正在筹备盛大的游行庆典。目前 C 国得知接下来会有 \(q\) 次游行计划,第 \(i\) 次游行希望从城市 \(s_i\) 出发,经过若干个城市后,在城市 \(t_i\) 结束,且在游行过程中,一个城市可以被经过多次。为了增加游行的乐趣,每次游行还会临时修建出 \(k\)(\(0 \le k \le 2\))条有向道路专门供本次游行使用,即其它游行计划不能通过本次游行修建的道路。
现在 C 国想知道,每次游行计划可能会经过多少座城市。
注意:临时修建出的道路可以不满足 C 国道路原有的特点。
观察发现
可以先Tarjan变成DAG,不影响可达性
再拓扑排序变成叶向树
然后考虑这一棵树
我们每次询问指挥临时加不超过2条边
我们就把起点,终点和临时加的边的端点拉出来建立虚树,
点很少
可以直接暴力,新加的边边权为0,成了新图
原图跑出 s 到达的点边,建反图跑出 t 到达的点边,最后取一个交累加进答案。
就做完了
P5314 [Ynoi2011] ODT
题目背景
【先咕咕咕
题目描述
给你一棵树,边权为 \(1\),有点权。
需要支持两个操作:
1 x y z:表示把树上 \(x\) 到 \(y\) 这条简单路径的所有点点权都加上 \(z\)。2 x y:表示查询与点 \(x\) 距离小于等于 \(1\) 的所有点里面的第 \(y\) 小点权。
平衡树+树剖
考虑先树剖
然后暴力显然是对于每个点建立一棵平衡树来维护与他距离小于等于1的点
这样显然很慢,所以有一个很巨的优化
平衡树里面平时只存轻儿子
这样路径修改的时候一个logn条重链,每条链修改也是logn的
对于单点查询,先把重儿子和自己扔进去
然后再查询
查完再删掉。
肯定有人说,啊啊啊这题要卡常数阿这题要奇妙常数优化阿,什么WBLT什么的
都不用-
直接PBDS神力
跑得飞快
#include<bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
constexpr int MAXN_VAL = 1000010;
int n, m;
struct edge{
int to, next;
} e[MAXN_VAL << 1];
int head[MAXN_VAL], cnt_edges = 0;
void addedge(int u, int v){
e[++cnt_edges].to = v;
e[cnt_edges].next = head[u];
head[u] = cnt_edges;
}
int nodeval[MAXN_VAL];
struct LzTreeManager {
#define LL long long
struct Rbtree{
tree<LL, null_type, std::less<LL>, rb_tree_tag, tree_order_statistics_node_update> st;
unsigned int insert_counter = 0;
void insert(int val_y) {
st.insert( (((LL)val_y) << 20) + insert_counter );
insert_counter++;
}
void erase(int val_y) {
auto it = st.lower_bound( ((LL)val_y) << 20 );
if (it != st.end() && (*it >> 20) == val_y) {
st.erase(it);
}
}
int kth(int k_idx) {
if (k_idx <= 0) {
return std::numeric_limits<int>::max();
}
if ((size_t)k_idx > st.size()) {
return std::numeric_limits<int>::max();
}
auto it = st.find_by_order(k_idx - 1);
return (int)(*it >> 20);
}
};
Rbtree rbtrees[MAXN_VAL];
void insert(int node_idx, int val_y) {
if (node_idx <= 0 || node_idx > n) return;
rbtrees[node_idx].insert(val_y);
}
void erase(int node_idx, int val_y) {
if (node_idx <= 0 || node_idx > n) return;
rbtrees[node_idx].erase(val_y);
}
int kth(int node_idx, int k_idx) {
if (node_idx <= 0 || node_idx > n) return std::numeric_limits<int>::max();
return rbtrees[node_idx].kth(k_idx);
}
} lztree;
struct BIT{
int sum[MAXN_VAL];
#define lowbit(x) (x&(-x))
inline void modify(int x, int val_change) {
if (x <= 0) return;
while(x <= n + 5) {
sum[x]+=val_change;
x+=lowbit(x);
}
}
inline int query(int x) {
int res=0;
while(x > 0) {
res+=sum[x];
x-=lowbit(x);
}
return res;
}
}bit;
int fa[MAXN_VAL],dep[MAXN_VAL],siz[MAXN_VAL];
int top[MAXN_VAL],son[MAXN_VAL],dfn[MAXN_VAL],id[MAXN_VAL],dfc = 0;
void dfs1(int u, int p_node){
fa[u] = p_node; dep[u] = dep[p_node]+1;
son[u] = 0; siz[u] = 1;
for(int i = head[u]; i; i = e[i].next){
int v = e[i].to;
if(v != p_node){
dfs1(v,u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
}
void dfs2(int u, int top_node){
top[u] = top_node; dfn[u] = ++dfc; id[dfc] = u;
if(son[u]) dfs2(son[u], top_node);
for(int i = head[u]; i; i = e[i].next){
int v = e[i].to;
if(v != fa[u] && v != son[u]){
dfs2(v,v);
lztree.insert(u, nodeval[v]);
}
}
}
void update(int u, int v, int val_change){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u,v);
if(fa[top[u]]) {
lztree.erase(fa[top[u]], bit.query(dfn[top[u]]));
}
bit.modify(dfn[top[u]], val_change);
bit.modify(dfn[u] + 1, -val_change);
if(fa[top[u]]) {
lztree.insert(fa[top[u]], bit.query(dfn[top[u]]));
}
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u,v);
if (v == top[v] && fa[v]) {
lztree.erase(fa[v], bit.query(dfn[v]));
}
bit.modify(dfn[v], val_change);
bit.modify(dfn[u] + 1, -val_change);
if (v == top[v] && fa[v]) {
lztree.insert(fa[v], bit.query(dfn[v]));
}
}
int query(int u_node, int k_idx){
int val_u = bit.query(dfn[u_node]);
int val_f = 0, val_s = 0;
lztree.insert(u_node, val_u);
if(fa[u_node]) {
val_f = bit.query(dfn[fa[u_node]]);
lztree.insert(u_node, val_f);
}
if(son[u_node]) {
val_s = bit.query(dfn[son[u_node]]);
lztree.insert(u_node, val_s);
}
int ans = lztree.kth(u_node, k_idx);
lztree.erase(u_node, val_u);
if(fa[u_node]) lztree.erase(u_node, val_f);
if(son[u_node]) lztree.erase(u_node, val_s);
return ans;
}
int main(){
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
cin>>n>>m;
for(int i = 1;i <= n;i++){
cin>>nodeval[i];
}
for(int i = 1;i < n;i++){
int u_edge,v_edge;
cin>>u_edge>>v_edge;
addedge(u_edge,v_edge); addedge(v_edge,u_edge);
}
dfs1(1,0);
dfs2(1,1);
for(int i = 1; i <= n; i++){
bit.modify(i, nodeval[id[i]]);
if (i + 1 <= n + 5) {
bit.modify(i + 1, -nodeval[id[i]]);
}
}
for(int i = 1;i <= m;i++){
int opt, q_x, q_y;
cin>>opt>>q_x>>q_y;
if(opt == 1){
int q_z;
cin>>q_z;
update(q_x,q_y,q_z);
}
else{
cout << query(q_x,q_y) << "\n";
}
}
return 0;
}
[NOI2021] 轻重边
小 W 有一棵 n 个结点的树,树上的每一条边可能是轻边或者重边。接下来你需要对树进行 m 次操作,在所有操作开始前,树上所有边都是轻边。操作有以下两种:
给定两个点 a 和 b,首先对于 a 到 b 路径上的所有点 x(包含 a 和 b),你要将与 x 相连的所有边变为轻边。然后再将 a 到 b 路径上包含的所有边变为重边。
给定两个点 a 和 b,你需要计算当前 a 到 b 的路径上一共包含多少条重边。
考虑染色
定义轻边为两个点颜色不一致时的边。
初始显然颜色都不一样。
然后对于每个修改操作,考虑就将路径中的所有点染色成一种新的颜色,这样就可以了。
可以用树剖实现,难点在查询。
#include <bits/stdc++.h>
using namespace std;
const int MX = 100005;
int n, ind, fir[MX], deep[MX], idx[MX], topf[MX], son[MX], sz[MX], fa[MX];
struct Edge { int to, nxt; } e[MX << 1];
struct Node { int lc, rc, cnt; void cln() { lc = rc = cnt = 0; } };
namespace sgtree {
int lazy[MX << 2];
Node tree[MX << 2];
void cln() {
memset(lazy, 0, sizeof lazy);
for (int i = 0; i < (MX << 2); i++) tree[i].cln();
}
void lazy_down(int k, int l, int r) {
if (!lazy[k]) return;
int x = lazy[k], mid = (l + r) >> 1; lazy[k] = 0;
lazy[k << 1] = lazy[k << 1 | 1] = x;
tree[k << 1] = { x, x, mid - l };
tree[k << 1 | 1] = { x, x, r - mid - 1 };
}
void update(int ul, int ur, int nl, int nr, int pos, int num) {
if (ul <= nl && nr <= ur) {
tree[pos] = { num, num, nr - nl };
lazy[pos] = num; return;
}
lazy_down(pos, nl, nr);
int mid = (nl + nr) >> 1;
if (ul <= mid) update(ul, ur, nl, mid, pos << 1, num);
if (mid < ur) update(ul, ur, mid + 1, nr, pos << 1 | 1, num);
Node ls = tree[pos << 1], rs = tree[pos << 1 | 1];
tree[pos] = { ls.lc, rs.rc, ls.cnt + rs.cnt + (ls.rc == rs.lc) };
}
Node query(int al, int ar, int nl, int nr, int pos) {
if (al <= nl && nr <= ar) return tree[pos];
lazy_down(pos, nl, nr);
int mid = (nl + nr) >> 1;
if (ar <= mid) return query(al, ar, nl, mid, pos << 1);
if (al > mid) return query(al, ar, mid + 1, nr, pos << 1 | 1);
Node w1 = query(al, ar, nl, mid, pos << 1);
Node w2 = query(al, ar, mid + 1, nr, pos << 1 | 1);
return { w1.lc, w2.rc, w1.cnt + w2.cnt + (w1.rc == w2.lc) };
}
}
void add(int a, int b, int pos) {
e[pos] = { b, fir[a] }; fir[a] = pos;
}
void dfs1(int x, int f, int d) {
deep[x] = d; fa[x] = f; sz[x] = 1;
for (int i = fir[x]; i; i = e[i].nxt) {
int h = e[i].to;
if (h != f) {
dfs1(h, x, d + 1); sz[x] += sz[h];
if (sz[h] > sz[son[x]]) son[x] = h;
}
}
}
void dfs2(int x, int tp) {
if (!x) return;
topf[x] = tp; idx[x] = ++ind;
dfs2(son[x], tp);
for (int i = fir[x]; i; i = e[i].nxt) {
int h = e[i].to;
if (h != son[x] && h != fa[x]) dfs2(h, h);
}
}
void range_update(int x, int y, int num) {
while (topf[x] != topf[y]) {
if (deep[topf[x]] < deep[topf[y]]) swap(x, y);
sgtree::update(idx[topf[x]], idx[x], 1, n, 1, num);
x = fa[topf[x]];
}
if (deep[x] < deep[y]) swap(x, y);
sgtree::update(idx[y], idx[x], 1, n, 1, num);
}
int range_query(int x, int y) {
bool flg = 0;
Node h, ans1 = { 0, 0, 0 }, ans2 = { 0, 0, 0 };
while (topf[x] != topf[y]) {
if (deep[topf[x]] < deep[topf[y]]) {
swap(x, y); flg ^= 1;
}
h = sgtree::query(idx[topf[x]], idx[x], 1, n, 1);
if (flg) ans2 = { h.lc, ans2.rc, ans2.cnt + h.cnt + (ans2.lc == h.rc) };
else ans1 = { ans1.lc, h.lc, ans1.cnt + h.cnt + (ans1.rc == h.rc) };
x = fa[topf[x]];
}
if (deep[x] < deep[y]) { swap(x, y); flg ^= 1; }
h = sgtree::query(idx[y], idx[x], 1, n, 1);
if (flg) ans2 = { h.lc, ans2.rc, ans2.cnt + h.cnt + (ans2.lc == h.rc) };
else ans1 = { ans1.lc, h.lc, ans1.cnt + h.cnt + (ans1.rc == h.rc) };
return ans1.cnt + ans2.cnt + (ans1.rc == ans2.lc);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int data; cin >> data;
while (data--) {
int m; cin >> n >> m;
ind = 0;
memset(fir, 0, sizeof fir);
memset(sz, 0, sizeof sz);
memset(fa, 0, sizeof fa);
memset(idx, 0, sizeof idx);
memset(son, 0, sizeof son);
memset(topf, 0, sizeof topf);
memset(deep, 0, sizeof deep);
sgtree::cln();
for (int j = 1; j < n; j++) {
int a, b; cin >> a >> b;
add(a, b, j); add(b, a, j + n - 1);
}
dfs1(1, 0, 1); dfs2(1, 1);
for (int j = 1; j <= n; j++)
sgtree::update(idx[j], idx[j], 1, n, 1, -idx[j]);
for (int j = 1; j <= m; j++) {
int opt, a, b; cin >> opt >> a >> b;
if (opt & 1) range_update(a, b, j);
else cout << range_query(a, b) << '\n';
}
}
return 0;
}
P6326 Shopping
马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有 n 个商店,并且它们之间的道路构成了一棵树的形状。
第 i 个商店只卖第 i 种物品,小苗对于这种物品的喜爱度是 wi,物品的价格为 ci,物品的库存是 di。但是商店街有一项奇怪的规定:如果在商店 u,v 买了东西,并且有一个商店 p 在 u 到 v 的路径上,那么必须要在商店 p 买东西。小葱身上有 m 元钱,他想要尽量让小苗开心,所以他希望最大化小苗对买到物品的喜爱度之和。
这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗?
钦定联通块必须包含x,显然可以O(n)作多重背包,所以考虑点分治
设 fi,j 为考虑了 dfs 序中 [i,n] 对应的节点,体积为 j 的最大价值。转移分两种情况:一个是选 i 对应的节点,然后对该节点的物品跑多重背包。另一个是不选 i 对应的节点,然后从该节点子树外转移过来。
P7952 [✗✓OI R1] 天动万象
帝君给了你一棵以 1 为根的有根树,每个点有一个点权 ai,要求支持以下几种操作:
1 u 表示查询以 u 为根的子树的最大值。
2 u 表示将 u 为根的子树内每一个节点的权值 同时变为 其所有 儿子 的权值之和,即对这棵子树执行 ∀x∈subtree(u),ax←∑y∈son(x)ay。其中 同时变为 的意思是「某个点操作时认为其儿子停留在这次操作未进行前的状态」。
显然如果树是一条链的话非常好维护,只需要dfs序区间向左移动即可。
对于每次这样的操作,链的叶子结点都会变成0,如果修改操作的复杂度和删除叶子结点的个树(也就是涉及链的个数有关),那复杂度就是对的。
所以我们就考虑如何将修改操作转为若干个链上的问题。
我们把树三度化,带撇节点没有权值

有一个巨大的发现,树上每次修改就会修改若干条左儿子链,然后合并所有带撇的节点。
转化为dfs序列,修改涉及叶子结点的链的个树就是叶子结点的个树,这是对的。
所以每次修改就变成了在dfs序列上链的个数(叶子节点个树)个链都向左移,然后再进行至多链的个数次单点查询+修改来清空带撇节点的值。需要顺序,考虑如何保证复杂度,每条左链动态维护最深的不为零的节点个数,然后依照dfs序倒序从后往前更新,更新一条链后向上合并左链顶点值。
于是问题就改变为了区间平移、单点修改与区间查询 max。
浙公网安备 33010602011771号