• cogs 1672. [SPOJ 375] 难存的情缘 树链剖分套线段树 易错! 全博客园最长最详细的题解


    1672. [SPOJ 375] 难存的情缘

    ★★★   输入文件:qtree.in   输出文件:qtree.out   简单对比
    时间限制:1 s   内存限制:256 MB

    【题目描述】

    一天机房的夜晚,无数人在MC里奋斗着。。。

    大家都知道矿产对于MC来说是多么的重要,但由于矿越挖越少,勇士们不得不跑到更远的地方挖矿,但这样路途上就会花费相当大的时间,导致挖矿效率低下。

    cjj提议修一条铁路,大家一致同意。

    大家都被CH分配了一些任务:

    zjmfrank2012负责绘制出一个矿道地图,这个地图包括家(当然这也是一个矿,毕竟不把家掏空我们是不会走的),和无数个矿,所以大家应该可以想出这是一个无向无环图,也就是一棵树。

    Digital_T和cstdio负责铺铁路。。所以这里没他们什么事,两位可以劳作去了。

    这个时候song526210932和RMB突然发现有的矿道会刷怪,并且怪的数量会发生变化。作为采矿主力,他们想知道从一个矿到另一个矿的路上哪一段会最困难。。。(困难值用zjm的死亡次数表示)。

    【输入格式】

    输入文件的第一行有一个整数N,代表矿的数量。矿的编号是1到N。

    接下来N-1行每行有三个整数a,b,c,代表第i号矿和第j号矿之间有一条路,在初始时这条路的困难值为c。

    接下来有若干行,每行是“CHANGE i ti”或者“QUERY a b”,前者代表把第i条路(路按所给顺序从1到M编号)的困难值修改为ti,后者代表查询a到b所经过的道路中的最大困难值。

    输入数据以一行“DONE”结束。

    【输出格式】

    对每个“QUERY”操作,输出一行一个正整数,即最大困难值。

    【样例输入】

    3

    1 2 1

    2 3 2

    QUERY 1 2

    CHANGE 1 3

    QUERY 1 2

    DONE

    【样例输出】

    1

    3

    【提示】

    对于60%的数据,1<=N<=50

    对于100%的数据,1<=N<=10000,1<=c<=1000000,1<=操作次数<=100000

    【来源】

    由GDFRWMY 改编自SPOJ 375 QTREE

    数据by cstdio

    啊哈,又来了坑人的树链剖分(太气人了 上一次考试就被它坑惨了) +线段树

    多练练也许就好了   所以!(隆重决定 猛敲一遍这一道题 <<意思就是说代码很长且心情愤怒)

    这一道题就是用一个树链剖分 放到线段树里

    啊 哈 来啦!~~~♪(^∇^*)

     

    大肆纪念  !!调了两天终于做出来了(无比艰辛 心情复杂)

    首先这一题有如下几个坑:

    1.边权转点权 因为要把这棵树进行树链剖分以后撂到线段树上 那线段树上只能存点的值啊 就要把边权转化为点权 那么这个该怎么办呢

    这是我的那个第一个DFS函数 在处理树链剖分的同时 不是会枚举到边嘛 就把边权放在y那个点上(就是放在儿子上),这样子就不用单独再把所有边全部枚举一遍 再把边权放在深度更大的一端上了♪(^∇^*)莫名开心

    2.在Change函数中是输入边的编号(按照输入顺序来编号) 把边权改为z

    呵呵 这一个方面 其实这个地方我处理的还不是非常好

    我后来想到一个方法 就是单独再设一个数组 aindex 数组 和 bindex 数组 aindex[i]表示第i条边的a端点是那一个点 这样子在Change函数的时候直接拿出来用就可以了

    我用了一个非常玄学的办法QAQ 再来瞅瞅我的代码

    首先我设了两个神奇的数组

    那个Index数组 额 就是记录是边(的一个端点)是第几个读入的   IndexDian就是记录第x条边的边权应该放在哪一个点上(是不是非常玄学QAQ 我坚信您现在还没很明白)

    再来看看存储:

    这是输入各个边的边权的那一段

    就是存起来嘛 

    然后:

    再次回到刚才的那个第一个DFS函数

    第88行的那个就是说。。。首先那个Index[x][i]里就是说x和v[x][i]之间的那一条边的编号

    整个IndexDian[Index[x][i]]=y就是第Index[x][i]条边的边权要放在y(儿子)那个点上QAQ

    那么在调用的时候怎么办呢?

    看到那个192行了吗(废话啊) 

    x不是就是输入边的编号吗 这样子就直接找出来应该修改那一个点的点权即可 ♪(^∇^*)(有点自豪)这样子就变的非常简单啦

    3.重头戏:怎样查询从a到b路径上的最大边权

    看似简单 然后我就猛敲了一顿 过了样例 就交了上去 结果。。10分!WA了9个点 只A了一个点(我太蒟了)

    这到底是为什么呢 ?经过数百次手造数据的验证 我终于找出了错误的原因

    我之前的写法是 直接按照求a到b的路径上的最大点权的做法来做的(看起来没有任何问题啊)

    事实上有反例

    快看下面这个图 ♪(^∇^*)

    非常粗糙 哈哈不要介意啦 (人家可是很用心地在写博客)

    先来解释一下这个图是什么意思吧

    黑色的那个12345 自然就是点的编号(最上面的那个1丢了 不要介意嘻嘻(#^.^#))

    红色的汉字的 一二三四 就是输入边的顺序(编号)

    蓝色的线段(额 这个不需要解释了吧) 就是连上一条边啊

    黄色的那个代表边权

    按照我们的做法 我们把边权全部放在了点上

    那么如果你要是求 2 到 5 路径上最大边权 没问题

    你看 2点点权2 1点点权0 3点点权6 5点点权4   那么最大值就是6啦 就是  1-3之间的那条边

    (一切看起来都没有任何问题QAQ)

    但是如果要是求4-5 路径上最大边权呢?很显然是3-5之间的那个4啊

    但是!你的程序如果按照路径最大点权 跑出来的却是6 为什么呢?

    原来 你把1-3的边权放在了3上 在4-5的路径上有3 自然3那个点的点权(其实不是4-5路径上的边权)就成了最大值

    这可咋整!?(困惑了我一个上午)

    我们再来分析一下第一个例子 就是求2-5的最大边权为什么没有出问题 我们惊奇地发现原来因为1点是0

    其实这个还是比较好解释和理解的

    在把边权撂到深度更大的那个点(简称儿子)以后

    a b 两点的 最近公共祖先的值 代表的是最近公共祖先往上(专业术语:深度更小)的那一条边的边权,其实我们是不考虑它的

    (仔细想想也许你就明白了)

    理解了错误原因后 那么我们怎么处理?这真是一个棘手的问题啊

    我们再来观察一下代码 

    看看那一段LCA

    加了一堆+1 的那一段绝望的注释仿佛已经解释了我所经历的一切

    就是说那个while函数以后 已经把x和y两个点放到了同一条重链上 为甚么这么说呢?

    (答案:自己去看定义!)<<算啦 开玩笑啦~♪(^∇^*)

    whike函数的停止条件是top[x]=top[y]那不是就到一条重链上了吗QAQ

    然后!奇迹的一段代码来临了  

    我们把x和y两个点跳到同一条重链上以后 需要把深度更大的那个点(这里定义成了y 你看有个swap函数)往上跳 和x相遇 也就是说要再求一下x和y之间那一段(重链上)的最大值 更新res

    然后呢?首先我们知道树链剖分的时候是先走重链 也就是说同一条重链的编号是连续的(自己去看定义!) 所以我们只需要 把x(就是更靠上的那一个点 其实现在就已经是LCA最近公共祖先了)的编号+1传进那个Max函数就可以了(是不是非常机智)

    x的编号+1也是非常好理解的 就是x到y之间不是有一条重链吗

    >>灵魂画手再次上线

    (默默地说:画技太差 我都看不下去了QAQ)

    那个紫色的就是一条重链 x和y已经跳到了如图所示的位置

    那么3那个点的pos就是1点的pos+1   (不信可以在代码中输出一下pos值瞅一眼♪(^∇^*))

    同理4的pos也是3那个点的pos值+1  (话说pos是啥?拍照时的那个吗  NoNoNo 是记录每个点是第几个访问到的)

    也就是说如果查询pos[1]+1  到 pos[4]  那么我们就可以非常愉快地把1那个点的点权完美地忽略掉了,就只求出3 4点的点权的较大值  也就是1-3  和 3-4 两条边的边权的较大值

    所以这一题就解决了

    下面贴出宏伟代码吧♪(^∇^*)  太艰辛了QAQ (<<等于我太蒟了)

      1 #include<cstdio>
      2 #include<iostream>
      3 #include<vector>
      4 #define ls (p*2)
      5 #define rs (p*2+1)
      6 #define mid (l+r>>1)
      7 #define INF 0x3f3f3f3f
      8 using namespace std;
      9 const int maxn=10005;
     10 int n,cnt;
     11 vector<int> v[maxn],w[maxn],Index[maxn];
     12 int IndexDian[maxn];
     13 int a[maxn];//要把边权转化为点权啊 
     14 int son[maxn],fa[maxn];
     15 int size[maxn],top[maxn],dep[maxn],dfn[maxn],pos[maxn];
     16 int sum[maxn<<2];
     17 void Dfs(int x)
     18 {
     19     size[x]=1; 
     20     for(int i=0;i<v[x].size();i++)
     21     {
     22         int y=v[x][i];
     23         if(!size[y])
     24         {
     25             fa[y]=x;
     26             dep[y]=dep[x]+1;
     27             IndexDian[Index[x][i]]=y;
     28             a[y]=w[x][i];//边权→点权 
     29             Dfs(y);
     30             size[x]+=size[y];
     31             if(size[y]>size[son[x]])
     32                 son[x]=y;//更新重儿子 
     33         }
     34     }
     35 }
     36 void Dfs(int x,int Tp)
     37 {
     38     top[x]=Tp;
     39     dfn[++cnt]=x;
     40     pos[x]=cnt;
     41     if(son[x])
     42     {
     43         Dfs(son[x],Tp);//先走重链 
     44     }
     45     for(int i=0;i<v[x].size();i++)
     46     {
     47         int to=v[x][i];
     48         if(!top[to])
     49         {
     50             Dfs(to,to);//轻链 
     51         }
     52     }
     53 }
     54 void Build(int p,int l,int r)
     55 {
     56     if(l==r)
     57     {
     58         sum[p]=a[dfn[l]];
     59         return;
     60     }
     61     Build(ls,l,mid);
     62     Build(rs,mid+1,r);
     63     sum[p]=max(sum[ls],sum[rs]);
     64     return;
     65 }
     66 void Change(int p,int l,int r,int posx,int z)
     67 {
     68     if(l==r)
     69     {
     70         sum[p]=z;
     71         return;
     72     }
     73     if(posx<=mid)
     74         Change(ls,l,mid,posx,z);
     75     else 
     76         Change(rs,mid+1,r,posx,z);
     77     sum[p]=max(sum[ls],sum[rs]);
     78     return;
     79 }
     80 int Max(int p,int l,int r,int s,int t)
     81 {
     82     if(s>r||t<l)
     83         return -INF;
     84     if(s<=l&&r<=t)
     85         return sum[p];
     86     return max(Max(ls,l,mid,s,t),Max(rs,mid+1,r,s,t));
     87 }
     88 int LCA(int x,int y)
     89 {
     90     int res=-INF;
     91     while(top[x]!=top[y])
     92     {
     93         if(dep[top[x]]<dep[top[y]])
     94             swap(x,y);
     95         res=max(res,Max(1,1,n,pos[top[x]],pos[x]));
     96         x=fa[top[x]];
     97     }
     98     if(dep[x]>dep[y])
     99         swap(x,y);
    100     res=max(res,Max(1,1,n,pos[x]+1,pos[y]));//+1  +1  +1  !!! 
    101     return res;
    102 }
    103 int main()
    104 {
    105 //    freopen("qtree.in","r",stdin);
    106 //    freopen("qtree.out","w",stdout);
    107     scanf("%d",&n);
    108     for(int i=1;i<n;i++)
    109     {
    110         int x,y,z;
    111         scanf("%d%d%d",&x,&y,&z);
    112         v[x].push_back(y);
    113         w[x].push_back(z);
    114         v[y].push_back(x);
    115         w[y].push_back(z);
    116         Index[x].push_back(i);
    117         Index[y].push_back(i);
    118     }
    119     Dfs(1);
    120     Dfs(1,1);
    121     Build(1,1,n);
    122     string s;
    123     while(cin>>s)
    124     {
    125         if(s[0]=='D')
    126             break;
    127         if(s[0]=='C')
    128         {
    129             int x,z;
    130             scanf("%d%d",&x,&z);
    131             x=IndexDian[x];
    132             Change(1,1,n,pos[x],z);
    133         }
    134         if(s[0]=='Q')
    135         {
    136             int x,y;
    137             scanf("%d%d",&x,&y);
    138             printf("%d
    ",LCA(x,y));
    139         }
    140     }
    141     return 0;
    142 }
    代码 点开看看 直接放出来会影响美观的♪(^∇^*)

    你以为这篇题解就结束了吗(天哪 至少有2000多字了)

    不 你想错了 我们要学会举一反三QAQ

    如果题目这样子出

    还是告诉你边权,让你求a到b路径上边权的和  QAQ

    这应该怎么办呢  其实这个还是比刚才的那个简单多了

    只需要求出来和之后减去最近公共祖先LCA的点权即可 因为毕竟是求和 一开始算上LCA也没什么大不了的 但是如果还是简单地按照求路径点权和就不行了 因为最近公共祖先的点权代表的是这条路径外的边的边权 我们要在用朴素(<<呵呵o(* ̄︶ ̄*)o)且简单(<<简单吗╭(╯^╰)╮)的做法  求出来路径大的点权和之后  再减去LCA的点权

    所以这一篇全博客园最宏伟的题解到此就结束了QAQ

    最后还有一个小彩蛋 至于是什么呢(QAQ♪(^∇^*))点一下下面的那个程序猫的图片就知道啦~

    感谢您认真(死撑着)看完了我的题解 啦啦 欢迎关注哦

  • 相关阅读:
    Yield Usage Understanding
    Deadclock on calling async methond
    How to generate file name according to datetime in bat command
    Run Unit API Testing Which Was Distributed To Multiple Test Agents
    druid的关键参数+数据库连接池运行原理
    修改idea打开新窗口的默认配置
    spring boot -thymeleaf-url
    @pathvariable和@RequestParam的区别
    spring boot -thymeleaf-域对象操作
    spring boot -thymeleaf-遍历list和map
  • 原文地址:https://www.cnblogs.com/Tidoblogs/p/11334172.html
Copyright © 2020-2023  润新知