BZOJ1036 - 1036 树链剖分

BZOJ1036 - 1036 树链剖分

1036: [ZJOI2008]树的统计Count

题目大意

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成

一些操作:

I. CHANGE u t : 把结点u的权值改为t

II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值

III. QSUM u v: 询问从点u到点v的路径上的节点的权值和

注意:从点u到点v的路径上的节点包括u和v本身

数据范围

1n300000,0q2000001≤n≤300000,0≤q≤200000

解题思路

树链剖分可以了解一下

AC代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int INF = 100000;
const int maxn = 300000;
typedef long long LL;
struct TREE {
    int l, r, Max, lazy;
    LL sum;
}tree[maxn * 4 + 5];
struct SPLIT {
    int size, dep, fa, son, id, top;
}split[maxn + 5];
int rid[maxn + 5], w[maxn + 5];
vector<int>E[maxn + 5];
int cnt;
//线段树
void Build(int L, int R, int x) {
    tree[x].l = L; tree[x].r = R, tree[x].lazy = 0, tree[x].Max = -INF; 
    if(L == R) {
        tree[x].Max = w[rid[L]];
        tree[x].sum = (LL)w[rid[L]];
        tree[x].lazy = 0;
        return ;
    }
    int mid = (L + R) / 2;
    Build(L, mid, x * 2);
    Build(mid + 1, R, x * 2 + 1);
    tree[x].sum = tree[x * 2].sum + tree[x * 2 + 1].sum;
    tree[x].Max = max(tree[x * 2].Max, tree[x * 2 + 1].Max);
}
void PushDown(int x) {
    if(tree[x].lazy) {
        tree[x * 2].lazy = tree[x].lazy;
        tree[x * 2 + 1].lazy = tree[x].lazy;
        tree[x * 2].sum = (LL)(tree[x * 2].r - tree[x * 2].l + 1) * (LL)tree[x].lazy;
        tree[x * 2 + 1].sum = (LL)(tree[x * 2 + 1].r - tree[x * 2 + 1].l + 1) * (LL)tree[x].lazy;
        tree[x].lazy = 0;
    }
}
LL QuerySum(int L, int R, int x) {
    if(L <= tree[x].l && tree[x].r <= R)return tree[x].sum;
    PushDown(x);
    int mid = (tree[x].l + tree[x].r) / 2;
    LL res = 0;
    if(L <= mid)res += QuerySum(L, R, x * 2);
    if(R > mid)res += QuerySum(L, R, x * 2 + 1);
    return res;
}
int QueryMax(int L, int R, int x) {
    if(L <= tree[x].l && tree[x].r <= R)return tree[x].Max;
    PushDown(x);
    int mid = (tree[x].l + tree[x].r) / 2;
    int Max = -INF;
    if(L <= mid)Max = max(Max, QueryMax(L, R, x * 2));
    if(R > mid)Max = max(Max, QueryMax(L, R, x * 2 + 1));
    return Max;
}
void Update(int L, int R, int num, int x) {
    if(L <= tree[x].l && tree[x].r <= R) {
        tree[x].sum = (LL)(tree[x].r - tree[x].l + 1) * num;
        tree[x].Max = num;
        tree[x].lazy = num;
        return ;
    }
    PushDown(x);
    int mid = (tree[x].l + tree[x].r) / 2;
    if(L <= mid)Update(L, R, num, x * 2);
    if(R > mid)Update(L, R, num, x * 2 + 1);
    tree[x].sum = tree[x * 2].sum + tree[x * 2 + 1].sum;
    tree[x].Max = max(tree[x * 2].Max, tree[x * 2 + 1].Max);
}
//处理出dep(深度),fa(父结点),size(子树大小),son(重结点)
void Dfs1(int u, int father, int depth) {
    split[u].dep = depth;
    split[u].fa = father;
    split[u].size = 1;
    for(int i = 0; i < E[u].size(); i++) {
        int v = E[u][i];
        if(v != split[u].fa) {
            Dfs1(v, u, depth + 1);
            split[u].size += split[v].size;
            //如果没有被访问或者该节点子树更大,更新重结点
            if(split[u].son == -1 || split[v].size > split[split[u].son].size)split[u].son = v;
        }
    }
}
//处理处,top(每条重链的顶端结点),id(每个结点剖分的编号,也就是Dfs的执行顺序),rid(该编号对应的结点)
void Dfs2(int u, int sta) {
    split[u].top = sta;
    split[u].id = cnt;
    rid[cnt] = u;
    cnt++;
    if(split[u].son == -1)return ;//叶子结点
    Dfs2(split[u].son, sta);//找出一条连续的重链
    for(int i = 0; i < E[u].size(); i++) {
        int v = E[u][i];
        if(v != split[u].son && v != split[u].fa)Dfs2(v, v);//v不是重结点且不是父结点,重新从v开始找重链
    }

}
//类似LCA
LL QueryPathSum(int x, int y) {
    LL res = 0;
    int fx = split[x].top, fy = split[y].top;
    while(fx != fy) {//判断是否在同一条重链上
        if(split[fx].dep >= split[fy].dep) {
            res += QuerySum(split[fx].id, split[x].id, 1);
            x = split[fx].fa;//走轻边
        }
        else {
            res += QuerySum(split[fy].id, split[y].id, 1);
            y = split[fy].fa;
        }
        fx = split[x].top, fy = split[y].top;
    }
    if(split[x].id < split[y].id)res += QuerySum(split[x].id, split[y].id, 1);
    else res += QuerySum(split[y].id, split[x].id, 1);
    return res;
}
int QueryPathMax(int x, int y) {
    int Max = -INF;
    int fx = split[x].top, fy = split[y].top;
    while(fx != fy) {
        if(split[fx].dep >= split[fy].dep) {
            Max = max(Max, QueryMax(split[fx].id, split[x].id, 1));
            x = split[fx].fa;
        }
        else {
            Max = max(Max, QueryMax(split[fy].id, split[y].id, 1));
            y = split[fy].fa;
        }
        fx = split[x].top, fy = split[y].top;
    }
    if(split[x].id < split[y].id)Max = max(Max, QueryMax(split[x].id, split[y].id, 1));
    else Max = max(Max, QueryMax(split[y].id, split[x].id, 1));
    return Max;
}
void UpdatePath(int x, int y, int z) {
    int fx = split[x].fa, fy = split[y].fa;
    while(fx != fy) {
        if(split[fx].dep >= split[fy].dep) {
            Update(split[fx].id, split[x].id, z, 1);
            x = split[fx].fa;
        }
        else {
            Update(split[fy].id, split[y].id, z, 1);
            y = split[fy].id;
        }
        fx = split[x].top, fy = split[y].top;
    }
    if(split[x].id <= split[y].id)Update(split[x].id, split[y].id, z, 1);
    else Update(split[y].id, split[x].id, z, 1);
}
int n, q;
char s[10];
int main() {
    scanf("%d", &n);
    for(int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    for(int i = 1; i <= n; i++)scanf("%d", &w[i]);
    for(int i = 1; i <= n; i++)split[i].son = -1;
    cnt = 1, Dfs1(1, -1, 1); Dfs2(1, 1);
    Build(1, n, 1);
    scanf("%d", &q);
    for(int i = 1; i <= q; i++) {
        int x, y;
        scanf("%s%d%d", s, &x, &y);
        if(s[1] == 'M')printf("%d\n", QueryPathMax(x, y));
        else if(s[1] == 'S')printf("%lld\n", QueryPathSum(x, y));
        else UpdatePath(x, x, y);
    }
    return 0;
}
posted @ 2018-08-09 16:46 呵呵!!! 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值