题目大意
-
Change k w:将第k条树枝边的边权改变为w。
-
Cover u v w:将节点u与节点v之间的边上的边权全改变为w。(w不会为负值)
-
Add u v w:将节点u与节点v之间的树枝上边的边权都增加w。
-
Max u v:询问节点u与节点v之间边权。
题目链接
分析
可以很快意识到这是一题树链剖分题,不过和传统的树链剖分不一样,本题的权值是附在边上而不是节点上的。于是我们可以考虑在dfs的过程中将边权转化在点权上。由于父节点和它的子节点是一对多的关系,所以只能是将father -> son这条边的边权附在son上,否则若附在fahter上,fahter有极大可能会被重复赋值。这显然不是我们希望看见的。
我们还需要两种修改操作,因此需要两个lazytag,细节很多,详见代码。
代码
准备阶段
//变量声明
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n, tot, cnt;
int head[N];
//深度,大小,父节点,重儿子,点的赋值
int dep[N], size[N],par[N], son[N], w[N];
//时间戳,对应的反函数,链顶
int dfn[N], pre[N], top[N];
//快读和链式前向星加边
inline int read(){
int x = 0, op = 1;
char ch = getchar();
while (!isdigit(ch)){
if (ch == '-') op = -1;
ch = getchar();
}
while (isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * op;
}
inline void add_edge(int u, int v, int w){
e[++tot] = {u, v, head[u], w};
head[u] = tot;
}
dfs
//在第一次dfs完成点的赋值
void dfs1(int u, int fa){
size[u] = 1;
dep[u] = dep[fa] + 1;
par[u] = fa;
for (int i = head[u];i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa) continue;
w[v] = e[i].val;
dfs1(v, u);
size[u] += size[v];
if (size[v] > size[son[u]])
son[u] = v;
}
}
void dfs2(int u, int tp){
top[u] = tp;
dfn[u] = ++cnt;
pre[cnt] = u;
if (son[u])
dfs2(son[u], tp);
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v != par[u] && v != son[u])
dfs2(v, v);
}
}
线段树
核心操作
线段树要完成操作:
- 区间加 add_interval
- 区间修改 modify_interval
- 单点修改 modify_point
- 区间最值访问 query_max
对1我们用懒标签add,对2和3用懒标签tag
想象一下对一个区间,如果先add再modify,区间修改的效果会覆盖区间加,所以可以确定优先级是tag比add大,也就是说tag会破坏掉add。我们把tag初始化为-1,add初始化为0.如果出现tag和add同时不为初始值的情况,一定是先区间修改,再区间加。因此可以确定关键操作push_down
int Max[N << 2], add[N << 2], tag[N << 2];
inline void push_up(int i){
Max[i] = max(Max[i << 1], Max[i << 1|1]);
}
inline void push_down(int i){
//如果tag和add都不为初始值,一定是先tag再add
if (tag[i] != -1){
add[i << 1] = add[i << 1|1] = 0;
tag[i << 1] = tag[i <<1|1] = tag[i];
Max[i << 1] = Max[i << 1|1] = tag[i];
tag[i] = -1;
}
if (add[i] == 0) return;
add[i << 1] += add[i];
add[i << 1|1] += add[i];
Max[i << 1] += add[i];
Max[i << 1|1] += add[i];
add[i] = 0;
}
建树
//tag的初始化在主函数中用memset实现
inline void buildTree(int i, int l, int r){
if (l == r){
Max[i] = w[pre[l]];
return;
}
buildTree(i << 1, l, mid);
buildTree(i << 1|1, mid + 1, r);
push_up(i);
}
实现区间加
inline void add_interval(int i, int l, int r, int crtl, int crtr, int val){
if (l > crtr || r < crtl)
return;
if (l >= crtl && r <= crtr){
add[i] += val;
Max[i] += val;
return;
}
push_down(i);
if (mid >= crtl)
add_interval(i << 1, l, mid, crtl, crtr, val);
if (mid < crtr)
add_interval(i << 1|1, mid + 1, r, crtl, crtr, val);
push_up(i);
}
实现区间和单点修改
inline void modify_interval(int i, int l, int r, int crtl, int crtr, int val){
if (l > crtr || r < crtl)
return;
if (l >= crtl && r <= crtr){
tag[i] = val;
Max[i] = val;
//tag会破坏掉add
add[i] = 0;
return;
}
push_down(i);
if (mid >= crtl)
modify_interval(i << 1, l, mid, crtl, crtr, val);
if (mid < crtr)
modify_interval(i << 1|1, mid + 1, r, crtl, crtr, val);
push_up(i);
}
inline void modify_point(int i, int l, int r, int pos, int val){
if (l == r){
tag[i] = val;
Max[i] = val;
//tag会破坏掉add
add[i] = 0;
return;
}
push_down(i);
if (mid >= pos)
modify_point(i << 1, l, mid, pos, val);
else
modify_point(i << 1|1, mid + 1, r, pos, val);
push_up(i);
}
区间最大值查询
inline int query_max(int i, int l, int r, int crtl, int crtr){
if (l > crtr || r < crtl)
return 0;
if (l >= crtl && r <= crtr){
return Max[i];
}
int res = -INF;
push_down(i);
if (mid >= crtl)
res = max(res, query_max(i << 1, l, mid, crtl, crtr));
if (mid < crtr)
res = max(res, query_max(i << 1|1, mid + 1, r, crtl, crtr));
return res;
}
树上操作
代码大体上一样,值得注意的是当u和v在同一条重链上时通过swap使dep[u] <= dep[v],此时编号为dfn[u]节点的权值是它的父节点到它的权值,不应该算进来
inline void tree_add(int u, int v, int val){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
add_interval(1, 1, n, dfn[top[u]], dfn[u], val);
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
//在同一条重链上,+1后必然还在这条链上
add_interval(1, 1, n, dfn[u] + 1, dfn[v], val);
}
inline void tree_modify(int u, int v, int val){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
modify_interval(1, 1, n, dfn[top[u]], dfn[u], val);
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
modify_interval(1, 1, n, dfn[u] + 1, dfn[v], val);
}
inline int tree_max(int u, int v){
int res = -INF;
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res = max(res, query_max(1, 1, n, dfn[top[u]], dfn[u]));
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
res = max(res, query_max(1, 1, n, dfn[u] + 1, dfn[v]));
return res;
}
主函数
值得一提的是 Change k 是将第k条树枝边的边权改变为w, 我们要简单判断一下第k条边u <–> v的边权附给了那个节点。只需要通过dfs顺序dfn的大小或者是u,v的深度判断那个节点是子节点,再进行单点修改即可
int main() {
//freopen("in.txt", "r", stdin);
memset(tag, -1, sizeof(tag));
n = read();
for (register int i = 1, x, y, z; i <= n - 1; ++i) {
x = read(), y = read(), z = read();
add_edge(x, y, z), add_edge(y, x, z);
}
dfs1(1, 0);
dfs2(1, 1);
buildTree(1, 1, n);
char opt[6] = {0};
int a = 0, b = 0, c = 0;
while (true){
scanf("%s", opt);
if (opt[0] == 'S')
break;
a = read(), b = read();
if (opt[0] == 'M')
printf("%d\n", tree_max(a, b));
if (opt[1] == 'h'){
a *= 2;
int u = e[a].fr, v = e[a].to;
int x = dfn[u] > dfn[v] ? u : v;
modify_point(1, 1, n, dfn[x], b);
}
if (opt[1] == 'o'){
c = read();
tree_modify(a, b, c);
}
if (opt[1] == 'd'){
c = read();
tree_add(a, b, c);
}
}
return 0;
}
完整代码
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cctype>
#define mid (l + r >> 1)
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n, tot, cnt;
int head[N];
int dep[N], size[N],par[N], son[N], w[N];
int dfn[N], pre[N], top[N];
struct edge{
int fr, to, nxt, val;
}e[N << 1];
inline int read(){
int x = 0, op = 1;
char ch = getchar();
while (!isdigit(ch)){
if (ch == '-') op = -1;
ch = getchar();
}
while (isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * op;
}
inline void add_edge(int u, int v, int w){
e[++tot] = {u, v, head[u], w};
head[u] = tot;
}
void dfs1(int u, int fa){
size[u] = 1;
dep[u] = dep[fa] + 1;
par[u] = fa;
for (int i = head[u];i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa) continue;
w[v] = e[i].val;
dfs1(v, u);
size[u] += size[v];
if (size[v] > size[son[u]])
son[u] = v;
}
}
void dfs2(int u, int tp){
top[u] = tp;
dfn[u] = ++cnt;
pre[cnt] = u;
if (son[u])
dfs2(son[u], tp);
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v != par[u] && v != son[u])
dfs2(v, v);
}
}
int Max[N << 2], add[N << 2], tag[N << 2];
inline void push_up(int i){
Max[i] = max(Max[i << 1], Max[i << 1|1]);
}
inline void push_down(int i){
if (tag[i] != -1){
add[i << 1] = add[i << 1|1] = 0;
tag[i << 1] = tag[i <<1|1] = tag[i];
Max[i << 1] = Max[i << 1|1] = tag[i];
tag[i] = -1;
}
if (add[i] == 0) return;
add[i << 1] += add[i];
add[i << 1|1] += add[i];
Max[i << 1] += add[i];
Max[i << 1|1] += add[i];
add[i] = 0;
}
inline void buildTree(int i, int l, int r){
if (l == r){
Max[i] = w[pre[l]];
return;
}
buildTree(i << 1, l, mid);
buildTree(i << 1|1, mid + 1, r);
push_up(i);
}
inline void add_interval(int i, int l, int r, int crtl, int crtr, int val){
if (l > crtr || r < crtl)
return;
if (l >= crtl && r <= crtr){
add[i] += val;
Max[i] += val;
return;
}
push_down(i);
if (mid >= crtl)
add_interval(i << 1, l, mid, crtl, crtr, val);
if (mid < crtr)
add_interval(i << 1|1, mid + 1, r, crtl, crtr, val);
push_up(i);
}
inline void modify_interval(int i, int l, int r, int crtl, int crtr, int val){
if (l > crtr || r < crtl)
return;
if (l >= crtl && r <= crtr){
tag[i] = val;
Max[i] = val;
add[i] = 0;
return;
}
push_down(i);
if (mid >= crtl)
modify_interval(i << 1, l, mid, crtl, crtr, val);
if (mid < crtr)
modify_interval(i << 1|1, mid + 1, r, crtl, crtr, val);
push_up(i);
}
inline void modify_point(int i, int l, int r, int pos, int val){
if (l == r){
tag[i] = val;
Max[i] = val;
add[i] = 0;
return;
}
push_down(i);
if (mid >= pos)
modify_point(i << 1, l, mid, pos, val);
else
modify_point(i << 1|1, mid + 1, r, pos, val);
push_up(i);
}
inline int query_max(int i, int l, int r, int crtl, int crtr){
if (l > crtr || r < crtl)
return 0;
if (l >= crtl && r <= crtr){
return Max[i];
}
int res = -INF;
push_down(i);
if (mid >= crtl)
res = max(res, query_max(i << 1, l, mid, crtl, crtr));
if (mid < crtr)
res = max(res, query_max(i << 1|1, mid + 1, r, crtl, crtr));
return res;
}
inline void tree_add(int u, int v, int val){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
add_interval(1, 1, n, dfn[top[u]], dfn[u], val);
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
add_interval(1, 1, n, dfn[u] + 1, dfn[v], val);
}
inline void tree_modify(int u, int v, int val){
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
modify_interval(1, 1, n, dfn[top[u]], dfn[u], val);
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
modify_interval(1, 1, n, dfn[u] + 1, dfn[v], val);
}
inline int tree_max(int u, int v){
int res = -INF;
while (top[u] != top[v]){
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res = max(res, query_max(1, 1, n, dfn[top[u]], dfn[u]));
u = par[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
res = max(res, query_max(1, 1, n, dfn[u] + 1, dfn[v]));
return res;
}
int main() {
//freopen("in.txt", "r", stdin);
memset(tag, -1, sizeof(tag));
n = read();
for (register int i = 1, x, y, z; i <= n - 1; ++i) {
x = read(), y = read(), z = read();
add_edge(x, y, z), add_edge(y, x, z);
}
dfs1(1, 0);
dfs2(1, 1);
buildTree(1, 1, n);
char opt[6] = {0};
int a = 0, b = 0, c = 0;
while (true){
scanf("%s", opt);
if (opt[0] == 'S')
break;
a = read(), b = read();
if (opt[0] == 'M')
printf("%d\n", tree_max(a, b));
if (opt[1] == 'h'){
a *= 2;
int u = e[a].fr, v = e[a].to;
int x = dfn[u] > dfn[v] ? u : v;
modify_point(1, 1, n, dfn[x], b);
}
if (opt[1] == 'o'){
c = read();
tree_modify(a, b, c);
}
if (opt[1] == 'd'){
c = read();
tree_add(a, b, c);
}
}
return 0;
}
测试样例
/*
*
input:
4
1 2 8
1 3 7
3 4 9
Max 2 4
Cover 2 4 5
Add 1 4 10
Change 1 16
Max 2 4
Stop
output:
9
16
*/