【BZOJ4458】GTY的OJ(树上超级钢琴)

点此看题面

大致题意: 给你一棵树,让你求出每一个节点向上的长度在([l,r])范围内的路径权值和最大的(m)条路径的权值总和。

关于此题的数列版本

此题的数列版本,就是比较著名的【BZOJ2006】[NOI2010] 超级钢琴一题了。

其实那道题目的思想,完全也可以套到这道题目上。

当然,如果你比较强大,写主席树等玄学算法+数据结构也是可以过的。

大致思路

首先,我们(dfs)一遍,求出(sum_i)表示编号为(i)的节点到根节点的权值和

考虑预处理出(RMQ_{i,j})表示编号为(i)的节点向上长度为(2^j)的路径中最小的(sum)

则对于每一个节点,从它出发能得到的最大值便是(sum_x-GetMin(x,l,r)),其中的(GetMin)可以用(RMQ)实现(O(logN))求解((RMQ)(O(1))的,但由于要找父亲,就变成了(O(logN)))。

我们可以把它们全部扔到一个堆里,每次取出堆顶,计算贡献,然后把这个区间拆成两半重新扔回堆中(具体做法可参考上面给出的链接,这里只是简单概括)。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define ten(x) (((x)<<3)+((x)<<1))
#define LL long long
#define N 500000
#define INF 1e18
using namespace std;
int n,m,L,R;
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
        #define pc(ch) (putchar(ch))
        int f,Top;char ch,*A,*B,Fin[Fsize],Stack[Fsize];
    public:
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void write(LL x) {if(x<0) pc('-'),x=-x;if(!x) return (void)(pc('0'));while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);}
}F;
class Class_RmqSolver
{
    private:
        #define LogN 20
    public:
        int fa[N+5][LogN],sum[N+5],Depth[N+5];
    private:
        struct key//存储每一个元素的信息
        {
            int pos,l,r,k,val;//pos表示起点,l,r存储可选择的区间,k存储当前选择的终点,val存储当前答案
            key(int x=0,int y=0,int z=0,int w=0,int v=0):pos(x),l(y),r(z),k(w),val(v){}
            inline friend bool operator < (key x,key y) {return x.val<y.val;}
        };
        priority_queue<key> q;//用一个堆进行维护 
        struct RMQ_data//RMQ求最小值
        {
            int MinPos,MinVal;
            RMQ_data(int x=0,int y=0):MinPos(x),MinVal(y){}
            inline friend bool operator < (RMQ_data x,RMQ_data y) {return x.MinVal<y.MinVal;}
        }RMQ[N+5][LogN];
        inline void Init() {for(register int i,j=1;j<LogN;++j) for(i=1;i<=n;++i) fa[i][j]=fa[fa[i][j-1]][j-1],RMQ[i][j]=min(RMQ[i][j-1],RMQ[fa[i][j-1]][j-1]);}//初始化
        inline int getfa(int x,int dep)//找父亲
        {
            for(register int i=0;i<LogN;++i) if(dep&(1<<i)) x=fa[x][i];
            return x;
        }
        inline RMQ_data get_min(int l,int r)//求最小值
        {
            register int i;register RMQ_data res=RMQ[l][0];
            for(i=LogN-1;i>=0;--i) if(Depth[fa[r][i]]>=Depth[l]) res=min(res,RMQ[r][i]),r=fa[r][i];
            return res;
        }
    public:
        inline void Solve()
        {
            register int i,t;register LL ans=0;register RMQ_data s;register key now;
            for(i=1;i<=n;++i) F.read(fa[i][0]),Depth[i]=Depth[fa[i][0]]+1;//初始化
            for(i=1;i<=n;++i) F.read(sum[i]),sum[i]+=sum[fa[i][0]],RMQ[i][0]=RMQ_data(i,sum[fa[i][0]]);//统计前缀和+初始化(这样可以不用DFS)
            for(Init(),F.read(m),F.read(L),F.read(R),i=1;i<=n;++i) if(Depth[i]>=L) t=min(R,Depth[i]),s=get_min(getfa(i,t-1),getfa(i,L-1)),q.push(key(i,getfa(i,t-1),getfa(i,L-1),s.MinPos,sum[i]-s.MinVal));//预处理把元素扔入堆中
            while(m--)
            {
                now=q.top(),q.pop(),ans+=now.val;//取出堆中元素,统计答案,然后拆成两半重新扔入堆中
                if(Depth[now.l]<Depth[now.k]) t=getfa(now.pos,Depth[now.pos]-Depth[now.k]+1),s=get_min(now.l,t),q.push(key(now.pos,now.l,t,s.MinPos,sum[now.pos]-s.MinVal));
                if(Depth[now.r]>Depth[now.k]) t=getfa(now.pos,Depth[now.pos]-Depth[now.k]-1),s=get_min(t,now.r),q.push(key(now.pos,t,now.r,s.MinPos,sum[now.pos]-s.MinVal));
            }
            F.write(ans);//输出答案
        }
}RmqSolver;
int main()
{
    return F.read(n),RmqSolver.Solve(),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4458.html