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

Posted nelson992770019

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树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;

 

 

以上是关于线段树FeyatCup——2.法法塔的奖励的主要内容,如果未能解决你的问题,请参考以下文章

一般线段树与权值线段树

Codeforces Round #625 Div1 C,二维偏序,排序+线段树

bzoj2161布娃娃 权值线段树

ZKW线段树

线段树

线段树