赤之夜

赤の夜


【问题描述】
数据结构大师 ddd 给你出了一道题:
给你一棵树,最开始点权为
这些点修改后的点权和。
【输入】
第一行两个数 n 和 m;
之后 n-1 行,第 i 行一个数 fa[i]
之后 m 行每行一个数 x 表示这次操作的点是x。
【输出】
输出一个数,即这 m 次询问的答案的和
保证答案在有符号 64 位整数范围内
【输入输出样例】
输入样例#1:
6 3
1 1 2 3 3
1 2 3
输出样例#1:
15
输入样例#2:
6 10
1 1 2 3 3
1 4 6 5 2 3 3 3 3 3
输出样例#2
115


今天考了这道水题……虽说是水题但是也耗费了我不少的脑力……

一开始我拿到这道题目原本乐滋滋的,以为就是线段树的板子题(谁知道我哪里蹦出来这么清奇的想法,有可能是这道题目太清奇了),然后都开始看T3了,结果发现T3有些麻烦又转回来定睛一看……咦,这好像不是线段树(连二叉树都没有保证,你哪里来的线段树?),然后我开始方了……开始胡思乱想了……结果花了很长一段时间才把它写下来。

某大佬写了个暴力竟然比我这个优化拿的分数还高(暴力出奇迹)。不过呢,这并不是说我的代码想法有问题,而是精度炸了!……不开森


暴力想法

我们看看这题,一会儿就可以想到一个暴力,就是模拟修改过程,当修改了一个点x时,我们就更新它的儿子与父亲,但是呢,这题目就是这点坏,它没有保证这棵树一定是二叉树或其它儿子节点较少的数,这样一些出题人为了卡这种想法可能就会澡极端数据:只有两层,第一层一个节点,其余的n-1个节点全部在第二层,然后每次修改全部都修改根(第一层的)节点,这样的代码就免不了TLE……所以我们还需要进一步思考……

我的想法

看完了暴力想法之后我们就马上可以发现题目难点所在,无非就是更新儿子节点的时候可能会碰到很多儿子节点从而导致代码运算量大,但是呢,我们不难想到有一个算法(艹,再故弄玄虚我打死你!),也是因为在区间更新时要更新众多儿子节点所以才加了一道优化——延迟标记,恩没错,这个算法就是线段树。看到这里有些人可能就马上就会有想法了……我来具体讲讲:

首先维护数组cso[],tso[],laz[],val[],fa[]这五个数组,分别表示当前节点儿子节点的个数,当前节点儿子节点被操作的总次数,当前节点有多少待更新的值需要传递给儿子节点,当前节点除去父亲节点上待更新的值的值(这里有些难理解,其实就存的是没有被父亲节点上囤积的值传递之前的值,比如第i个节点原本的值应该是3,但是父亲节点的延迟标记为1,所以val[i]的值就是2),当前节点的父亲节点。

更新:当第x个节点被操作了,首先自身的值需要+1(val[x]++),因为按照题目描述它的儿子节点也需要+1,所以这个节点上的延迟标记+1(laz[x]++),然后就是更新父亲节点(val[fa[x]]++),因为这个节点的父亲的节点被更新了,所以这个节点的父亲节点的儿子的被操作次数就要+1(tso[fa[i]]++),又因为这个节点的父亲节点也被更新了,所以这个节点的父亲节点的父亲节点的儿子节点的被操作次数(有点晕?不妨多看几遍)也要+1(tso[fa[fa[i]]]++),到此,一次更新就算完成了!

访问:根据上面数组的定义,我们马上就可以推出操作第i个节点时候的结果,首先要先把父亲节点的值加上去,这时候父亲节点的值就是val[fa[i]]+laz[fa[fa[i]]],然后就是需要加上自己的值val[i]+laz[fa[i]],接下来就需要知道自己所有儿子值的和,首先tso[i]是无需质疑的,那么还需要添加什么呢?答案很简单,就是laz[i]*cso[i],这里需要理解一下,第i个节点有laz[i]囤积再这个值上,原本应该给每个儿子都添加过去,现在全部都囤在这里,没有去更新儿子节点,所以求儿子节点的值的时候需要在原本的tso[i]的基础上加上这个值。

奉送代码

//#pragma GCC optimize(3)
#include<cstdio>
#define LL long long
using namespace std;
LL n,m,laz[100005],tso[100005],val[100005],cso[100005],fa[100005],ans;
inline int read(){
    int ret=0;char ch=getchar();
    while (ch<'0'||ch>'9')ch=getchar();
    while (ch>='0'&&ch<='9')ret=ret*10+ch-48,ch=getchar();
    return ret;
}
int main(){
    freopen("chiye.in","r",stdin);
    freopen("chiye.out","w",stdout);
    n=read(),m=read();
    for (int i=2;i<=n;i++)fa[i]=read(),cso[fa[i]]++;
    for (int i=1;i<=m;i++){
        int x=read();
        laz[x]++,val[x]++,val[fa[x]]++,tso[fa[x]]++,tso[fa[fa[x]]]++;
        val[0]=0;
        LL tpo=val[x]+laz[fa[x]]+laz[x]*cso[x]+tso[x]+val[fa[x]]+laz[fa[fa[x]]];
        ans+=tpo;
//      printf("%d\n",tpo);
    }
    printf("%lld\n",ans);
}

ps.这道题目其实还应该再添加一个文件流来读入大数据,但是我在这里就偷懒不写啦……

结尾

至此,这道题目就解完了……代码很短,思维含量也不高,但是却耗费了我不少的时间,由此看出我真的还需要继续好好学习,这样才可以在未来的竞赛中取得好成绩!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值