线段树FeyatCup——2.法法塔的奖励
Posted nelson992770019
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树FeyatCup——2.法法塔的奖励相关的知识,希望对你有一定的参考价值。
首先感谢并膜拜两位大佬 wyl8899 & 法法塔 感谢两位的出题!
题目:
法法塔是很喜欢写程序的。所以冒着就算码农屌丝一辈子的风险也大无畏地写着程序。 输入数据: 第一行一个数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;
以上是关于线段树FeyatCup——2.法法塔的奖励的主要内容,如果未能解决你的问题,请参考以下文章