欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

【线段树】【FeyatCup】——2.法法塔的奖励

发布时间:2025/7/25 编程问答 190 豆豆
生活随笔 收集整理的这篇文章主要介绍了 【线段树】【FeyatCup】——2.法法塔的奖励 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

首先感谢并膜拜两位大佬 wyl8899 & 法法塔 感谢两位的出题!

题目:

法法塔是很喜欢写程序的。所以冒着就算码农屌丝一辈子的风险也大无畏地写着程序。
码农们为了表彰法法塔的坚持与执着,决定给法法塔颁布奖励,为了体现码农的屌丝气质,奖励也将由法法塔自己做出选择!
所有的奖励被组织成了一棵树的形态,每个点都有一个权值。法法塔首先选择一个子树,然后选择从该子树内的一个叶子节点到该子树的根的一条路径,将路径上节点的权值依次排成一个序列,求得这个序列的最长不下降子序列长度,这个长度就是他能得到的奖品数目。要求该子树的根必须被选择。
接下来就是法法塔进行选择的时间了。尽可能多地得到奖品是自然的。那么法法塔到底能够得到多少奖品呢?这是个很纠结的问题,他决定交给你来解决。对于每个子树,他都希望你能求出他首先选择了这个子树后,他能得到的最多的奖品数目。

输入数据:

第一行一个数n,描述树上节点的个数。
接下来一行n个数,描述树上每个点的父亲,点1为根,其父亲设为-1,其余每个点的父亲的标号都小于自己。 
接下来一行n个数,表示树上每个点的权值。

输出数据:

一行n个数,第i个数表示法法塔选择了以第i个节点为根的子树后,他可以获得的最多的奖品个数。

数据范围:

n<=100000,每个数的权值都是1到n的正整数。

  拆分题意,这道题是求树上求最长不下降子序列,但是如果将每一条边单独拿出来进行dp是会爆空间的,所以我们考虑别的方法。

 

 


 

 

参考:

 

https://www.cnblogs.com/chhokmah/p/10550155.html

 

https://www.cnblogs.com/Mychael/p/8665589.html

https://www.luogu.org/blog/wcr20020327/xin-ren-di-di-yi-ci-blog-xian-duan-shu

 

动态开点线段树&&线段树合并:

  顾名思义,动态开点线段树就是在线段树的基础上实现动态开点,达到减小空间的方法。与原来的线段树有一点不同,动态开点线段树拥有左儿子(ls)与右儿子(rs),便于进行线段树与点之间的组合和更新。

  举个例子,单点构造是在原有线段树上开启新的点(要注意取地址符的使用):

 

1 void change(int &o,int l,int r,int a,int b){//a=pos,b=size 2 if (!o) o=++cnt; 3 if (l==r){ 4 g[o].sum=max(g[o].sum,b); 5 return; 6 } 7 int mid=(l+r)/2; 8 if (a<=mid) change(g[o].ls,l,mid,a,b); 9 else change(g[o].rs,mid+1,r,a,b); 10 g[o].sum=max(g[g[o].ls].sum,g[g[o].rs].sum); 11 }

 

其余的不再赘述,而线段树合并则是将两个线段树合并在一起(废话),参见代码:

 

1 //Mychael大大的代码 2 int merge(int u,int v){ 3 if (!u) return v;//如果没有u点,则返回v做根 4 if (!v) return u;//类似上面 5 int t = ++cnt;//如果两边都有,就新建t点作为根 6 sum[t] = sum[u] + sum[v];//融合两个点,但是在这个程序中需要改为更新最大值 7 ls[t] = merge(ls[u],ls[v]);//递归修改 8 rs[t] = merge(rs[u],rs[v]); 9 return t; 10 }

 

经过上面两个操作,这道题的解法应该已经大致明晰了呢:

我们在一个子树中找到当前最长不下降子序列中的最大值,并在其基础上加1即可。

 

//continue #include<bits/stdc++.h> using namespace std; const int N=1e6+10; int head[N],cnt,num[N],rt[N],n,ans[N]; struct edge{int to,next; }e[N]; struct line{int ls,rs,sum; }tr[N<<1]; void addedge(int from,int to){e[++cnt].to=to;e[cnt].next=head[from];head[from]=cnt; } void change(int &o,int l,int r,int siz,int pos){ //动态开点 if(!o) o=++cnt;if(l==r){tr[o].sum=max(tr[o].sum,siz);return;}int mid=(l+r)>>1;if(pos<=mid) change(tr[o].ls,l,mid,siz,pos);else change(tr[o].rs,mid+1,r,siz,pos);tr[o].sum=max(tr[tr[o].ls].sum,tr[tr[o].rs].sum); } int query(int o,int l,int r,int a,int b){//查询区间最大值if(!o) return 0;if(l==a&&r==b) return tr[o].sum;int mid=(l+r)>>1;if(b<=mid) return query(tr[o].ls,l,mid,a,b);else if(a>mid) return query(tr[o].rs,mid+1,r,a,b);else return max(query(tr[o].ls,l,mid,a,mid),query(tr[o].rs,mid+1,r,mid+1,b)); } int merge(int a,int b,int l,int r){if(!a||!b) return a+b;if(l==r){tr[a].sum=max(tr[a].sum,tr[b].sum);return a;}int mid=(l+r)>>1;tr[a].ls=merge(tr[a].ls,tr[b].ls,l,mid);tr[a].rs=merge(tr[a].rs,tr[b].rs,mid+1,r);tr[a].sum=max(tr[a].sum,tr[b].sum);return a; } void dfs(int o){//遍历整个树,将线段树合并求最大值int j=0;for(int u=head[o];u;u=e[u].next){int v=e[u].to;dfs(v);j=merge(j,rt[v],1,n);}ans[o]=query(j,1,n,1,num[o])+1;change(j,1,n,ans[o],num[o]);rt[o]=j; } int main(){scanf("%d",&n);int fa;for(int i=1;i<=n;i++){scanf("%d",&fa);if(fa!=-1)addedge(fa,i);}for(int i=1;i<=n;i++) scanf("%d",&num[i]);cnt=0;dfs(1);for(int i=1;i<=n;i++) printf("%d ",ans[i]);return 0; }

 

转载于:https://www.cnblogs.com/Nelson992770019/p/11160434.html

总结

以上是生活随笔为你收集整理的【线段树】【FeyatCup】——2.法法塔的奖励的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。