【贪心+堆+ST算法】BZOJ 4458: GTY的OJ 题解

写在前面

失踪人口在8个月的文化课狂补和4天的适(tui)应(fei)后,应该算正式回归了。(目前为半失踪人口)

本来想换个Blog当作重新开始,但反正都退役了,就不瞎折腾了…

BZOJ 4458: GTY的OJ

https://www.lydsy.com/JudgeOnline/problem.php?id=4458

题目概述

给出一棵带有点权的 n 个节点的树,在树上选出 m 个长度在 L R 之间的路径,求这 m 条路径的权值最大值。

n , m 10 5

解题分析

暴力肯定不可取,所以需要换个思路。

等等,这好像有一道题

BZOJ 2006: [NOI2010]超级钢琴

https://www.lydsy.com/JudgeOnline/problem.php?id=2006

好像是上面那题的序列版,处理序列肯定比在树上容易,所以先解决这个。

假设我们抓到一个点 i ,那么这个点所形成的区间的起点就在 [ i R + 1 , i L + 1 ] 内,权值是多少?通过构造前缀和就发现, s u m [ i ] s u m [ s ]

然后题目又要求前 m 大,所以想到了堆,将所有的序列放在堆内,取出前 m 个最大的加起来就是答案。但是区间这么多,所以还需要进行优化。

对于一个序列

无标题1

假设现在我们已经找到了终点为i的最大序列,起点为t,那么现在把它取出算入答案,为了不算重,下一次我们将可覆盖的范围拆为两段,然后在这之间寻找最大值,加入堆中。

那么这样堆中最多只有 n + m 个元素,问题就剩下了一个:如何求最大值?

再看看这个式子 s u m [ i ] s u m [ s ] ,其中 s u m [ i ] 是固定的,所以就要求 s u m [ s ] 最小,在一个特定要求的范围内找最小值,什么,RMQ啊!

总结一下,求出 s u m 数组后RMQ处理,初始将对每个位置为终点进行处理,寻找最大的区间并放入堆中。询问时找出最大值的区间 [ p o s , i ] ,那么将 p o s 两端分别当作两个新的范围处理。

代码如下

#include<cmath>
#include<cstdio>
#include<algorithm>
#define maxn 500005
#define LL long long
using namespace std;
int n,k,L,R,len,f[maxn][22];
LL sum[maxn],ans;
inline void readi(int &x){
    x=0; char ch=getchar(),lst='+';
    while ('0'>ch||ch>'9') {lst=ch; ch=getchar();}
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    if (lst=='-') x=-x;
}
int Rmin(int i,int j){return sum[i-1]>sum[j-1]?j:i;}
int getR(int L,int R){int j=log2(R-L+1); return Rmin(f[L][j],f[R-(1<<j)+1][j]);}
void makeR(){
    for (int j=1,to=log2(n);j<=to;j++)
        for (int i=1;i<=n+1-(1<<j);i++)
            f[i][j]=Rmin(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
void _init(){
    freopen("piano.in","r",stdin);
    freopen("piano.out","w",stdout);
    readi(n); readi(k); readi(L); readi(R); sum[0]=0;
    for (int i=1,x;i<=n;i++) {readi(x); sum[i]=sum[i-1]+x; f[i][0]=i;}
    makeR();
}
struct data{
    int id,L,R,t; LL tem;
    data (int id=0,int L=0,int R=0):id(id),L(L),R(R) {t=getR(L,R); tem=sum[id]-sum[t-1];}
    bool operator < (const data b)const{
        return tem<b.tem;
    }
}hep[maxn<<1];
void putH(data a){
    hep[++len]=a;
    for (int son=len;son!=1&&hep[son>>1]<hep[son];son>>=1) swap(hep[son],hep[son>>1]);
}
data getH(){
    data ans=hep[1]; hep[1]=hep[len--]; int fa=1,son;
    while ((fa<<1)<=len){
        son=fa<<1;
        if (son<len&&hep[son]<hep[son+1]) son++;
        if (hep[fa]<hep[son]) {swap(hep[fa],hep[son]); fa=son;}
        else break;
    }
    return ans;
}
void _solve(){
    len=ans=0;
    for (int i=L;i<=n;i++) putH(data(i,max(i-R+1,1),i-L+1));
    for (int i=1;i<=k;i++){
        data te=getH(); ans+=te.tem;
        if (te.L<te.t) putH(data(te.id,te.L,te.t-1));
        if (te.t<te.R) putH(data(te.id,te.t+1,te.R));
    }
    printf("%lld",ans);
}
int main()
{
    _init();
    _solve();
    return 0;
}

然后再说这题。树上的话有许多细节,但运用倍增的思想结合超级钢琴的思路也就好了。

顺便说一句,手写堆比STL快…

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500005
#define LL long long
using namespace std;
int n,m,L,R,len,tot,ro,stp[maxn],fa[maxn][22],f[maxn][22];
LL ans,sum[maxn];
inline void readi(int &x){
    x=0; char ch=getchar(),lst='-';
    while ('0'>ch||ch>'9') {lst=ch; ch=getchar();}
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    if (ch=='-') x=-x;
}
int minR(int i,int j){return sum[fa[i][0]]<sum[fa[j][0]]?i:j;}
int getF(int x,int dep){
    for (int j=tot;j;j--)
        if (dep&(1<<j)) x=fa[x][j];
    return x;
}
int askR(int L,int R){
    int tem=f[L][0];
    for (int j=tot;j;j--)
        if (stp[fa[R][j]]>=stp[L]) {tem=minR(tem,f[R][j]); R=fa[R][j];}
    return tem;
}
void makeR(){
    for (int j=1;j<=tot;j++)
        for (int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    for (int j=1;j<=tot;j++)
        for (int i=1;i<=n;i++)
            if (stp[i]>=(1<<j)) f[i][j]=minR(f[i][j-1],f[fa[i][j-1]][j-1]);
}
void _init(){
    freopen("oj.in","r",stdin);
    freopen("oj.out","w",stdout);
    readi(n); tot=0;
    memset(fa,0,sizeof(fa));
    memset(sum,0,sizeof(sum));
    for (int i=1;i<=n;i++){
        readi(fa[i][0]);
        if (!fa[i][0]) ro=i;
    }
    for (int i=1,x;i<=n;i++){
        readi(x); sum[i]=sum[fa[i][0]]+x;
        stp[i]=stp[fa[i][0]]+1; f[i][0]=i; tot=max(tot,stp[i]);
    }
    tot=log2(tot); readi(m); readi(L); readi(R); makeR();
}
struct data{
    int id,L,R,t; LL num;
    data (int id=0,int L=0,int R=0):id(id),L(L),R(R){
        t=askR(L,R); num=sum[id]-sum[fa[t][0]];
    }
    bool operator < (const data b)const{
        return num<b.num;
    }
}hep[maxn<<1];
void putH(data a){
    hep[++len]=a;
    for (int son=len;son!=1&&hep[son>>1]<hep[son];son>>=1)
        swap(hep[son],hep[son>>1]);
}
data getH(){
    data ans=hep[1]; hep[1]=hep[len--];
    for (int fa=1,son;(fa<<1)<=len;fa=son){
        son=fa<<1;
        if (son<len&&hep[son]<hep[son+1]) son++;
        if (hep[fa]<hep[son]) swap(hep[fa],hep[son]); else break;
    }
    return ans;
}
void _solve(){
    ans=len=0;
    for (int i=1;i<=n;i++)
        if (stp[i]>=L){
            int l=max(getF(i,R-1),ro),r=getF(i,L-1);
            putH(data(i,l,r));
        }
    for (int i=1;i<=m;i++){
        data a=getH(); ans+=a.num;
        if (a.t!=a.L){
            int Fl=fa[a.t][0];
            putH(data(a.id,a.L,Fl));
        }
        if (a.t!=a.R){
            int le=stp[a.id]-stp[a.t],Fr=getF(a.id,le-1);
            putH(data(a.id,Fr,a.R));
        }
    }
    printf("%lld",ans);
}
int main()
{
    _init();
    _solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/try__jhf/article/details/80971902