• 树链剖分


    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    using namespace std;
    struct node
    {
        int x,y,v;
    };
    node edge[10001];
    char c[10];
    int t,n,z,m,a,b,f1,f2,root,w[10001],tree[40004],top[10001],size[10001],son[10001],head[10001],next[20002],list[20002],fa[10001],dep[10001]; 
    //tree[]为线段树,size[v]表示以v为根节点的子树的结点个数,son[v]表示v的重儿子,fa[v]表示v的父节点,dep[v]表示树的深度,top[v]表示v所在链的顶端节点,w[v]表示点v与其父亲的连边在线段树中的位置。
    //n表示节点数,m表示边数,z表示线段树中的边数。
    void insert(int x,int y) { next[++m]=head[x]; head[x]=m; list[m]=y; } void dfs(int v) //dfs求节点的size son dep { size[v]=1; son[v]=0; for (int i=head[v];i;i=next[i]) if (list[i]!=fa[v]) { fa[list[i]]=v; dep[list[i]]=dep[v]+1; dfs(list[i]); if (size[list[i]]>size[son[v]]) son[v]=list[i]; size[v]+=size[list[i]]; } } void build_tree(int v,int tp) //求top w { w[v]=++z; top[v]=tp; if (son[v]!=0) build_tree(son[v],top[v]); for (int i=head[v];i;i=next[i]) if (list[i]!=son[v]&&list[i]!=fa[v]) build_tree(list[i],list[i]); } void updata(int root,int low,int high,int pos,int x) //更改某条边(即线段树中第pos个点)的值 { if (pos>high||pos<low) return; if (low==high) { tree[root]=x; return; } int mid=(low+high)/2,ls=root*2,rs=ls+1; updata(ls,low,mid,pos,x); updata(rs,mid+1,high,pos,x); tree[root]=max(tree[ls],tree[rs]); } int maxi(int root,int low,int high,int l,int r) 求区间[l,r]的最大值,线段树。 { if (l>high||r<low) return 0; if (l<=low&&high<=r) return tree[root]; int mid=(low+high)/2,ls=root*2,rs=ls+1; return max(maxi(ls,low,mid,l,r),maxi(rs,mid+1,high,l,r)); } int find(int va,int vb) 寻找va->vb路径上边的最大值,线段树维护。 { int f1=top[va],f2=top[vb],tmp=0; while (f1!=f2) { if (dep[f1]<dep[f2]) { swap(f1,f2); swap(va,vb); } tmp=max(tmp,maxi(1,1,z,w[f1],w[va])); va=fa[f1];f1=top[va]; } if (va==vb) return tmp; if (dep[va]>dep[vb]) swap(va,vb); return max(tmp,maxi(1,1,z,w[son[va]],w[vb])); } int main() { scanf("%d",&t); while (t--) { memset(size,0,sizeof(size)); memset(head,0,sizeof(head)); memset(next,0,sizeof(next)); memset(tree,0,sizeof(tree)); scanf("%d",&n); root=(n+1)/2; fa[root]=z=dep[root]=m=0; for (int i=1;i<n;i++) { scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].v); insert(edge[i].x,edge[i].y); insert(edge[i].y,edge[i].x); } dfs(root); build_tree(root,root); for (int i=1;i<n;i++) { if (dep[edge[i].x]>dep[edge[i].y]) swap(edge[i].x,edge[i].y); updata(1,1,z,w[edge[i].y],edge[i].v); } } scanf("%s",c); while (c[0]!='D') { scanf("%d%d",&a,&b); if (c[0]=='Q') printf("%d ",find(a,b)); else updata(1,1,z,w[edge[a].y],b); scanf("%s",c); } return 0; }

    以下为转载内容:

        在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。

        树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
        记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),w[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。

        重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
        轻儿子:v的其它子节点。
        重边:点v与其重儿子的连边。
        轻边:点v与其轻儿子的连边。
        重链:由重边连成的路径。
        轻链:轻边。

        剖分后的树有如下性质:
        性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
        性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
       

        算法实现:
        我们可以用两个dfs来求出fa、dep、siz、son、top、w。
        dfs_1:把fa、dep、siz、son求出来,比较简单,略过。
        dfs_2:⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记w[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
               ⒉对于v的各个轻儿子u,显然有top[u] = u,并且w[u] = totw+1,进行dfs_2过程。
               这就求出了top和w。
        将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

        修改操作:例如将u到v的路径上每条边的权值都加上某值x。
        一般人需要先求LCA,然后慢慢修改u、v到公共祖先的边。而高手就不需要了。
        记f1 = top[u],f2 = top[v]。
        当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新u到f1的父边的权值(logn),并使u = fa[f1]。
        当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
        重复上述过程,直到修改完成。

        求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。
        就这样,原问题就解决了。鉴于鄙人语言表达能力有限,咱画图来看看:[转载]树链剖分

        如右图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。

        当要修改11到10的路径时。
        第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
        第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
        第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
        第四次迭代:f1 = f2且u = v,修改结束。

  • 相关阅读:
    jMeter 里 CSV Data Set Config Sharing Mode 的含义详解
    如何使用 jMeter Parallel Controller
    使用 Chrome 开发者工具 coverage 功能分析 web 应用的渲染阻止资源的执行分布情况
    使用 Chrome 开发者工具的 lighthouse 功能分析 web 应用的性能问题
    关于 SAP 电商云首页加载时触发的 OCC API 请求
    SAP UI5 确保控件 id 全局唯一的实现方法
    SAP 电商云 Accelerator 和 Spartacus UI 的工作机制差异
    介绍一个好用的能让网页变成黑色背景的护眼 Chrome 扩展应用
    Chrome 开发者工具 performance 标签页的用法
    Client Side Cache 和 Server Side Cache 的区别
  • 原文地址:https://www.cnblogs.com/ws-fqk/p/4639315.html
Copyright © 2020-2023  润新知