首先感谢并膜拜两位大佬 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; }
原文地址:https://www.cnblogs.com/Nelson992770019/p/11160434.html
时间: 2024-10-17 05:23:28