BZOJ2006[NOI2010]超级钢琴&&BZOJ4458 GTY的OJ——堆+RMQ

Description

小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的
音乐。 这架超级钢琴可以弹奏出n个音符,编号为1至n。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级
和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的
所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。
小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。
我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最
大值是多少。
Input

第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所
包含音符个数的下限和上限。 接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。
N<=500,000
k<=500,000
-1000<=Ai<=1000,1<=L<=R<=N且保证一定存在满足条件的乐曲
Output

只有一个整数,表示乐曲美妙度的最大值。

Sample Input

4 3 2 3

3

2

-6

8
Sample Output

11

【样例说明】

共有5种不同的超级和弦:

音符1 ~ 2,美妙度为3 + 2 = 5

音符2 ~ 3,美妙度为2 + (-6) = -4

音符3 ~ 4,美妙度为(-6) + 8 = 2

音符1 ~ 3,美妙度为3 + 2 + (-6) = -1

音符2 ~ 4,美妙度为2 + (-6) + 8 = 4

最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。


我们先考虑最朴素的暴力,先做一遍前缀和,然后枚举每一个右端点,然后枚举左边距离其为L~R的点,统计答案的值(sum[x]-sum[p-1]),扔到一个堆里。最后在堆里取前k大的就行了。
我们发现一个右端点对应的一段区间,只会选中区间中的某一个点,显然我们可以用RMQ求出这个点的位置(这样的RMQ会优点鬼畜,大家可以看代码理解,注意getmin函数)。所以我们的做法是先 O ( n ) O(n) O(n)扫一遍,然后将其左边区间的最优点用RMQ找出来,然后用一个struct记录这些信息,将它们扔到一个堆里面。
之后每次堆取最大的值,但是实际上取完之后只是删除了最优点,对于区间L~R的其他点,依旧会与x点产生一些不错的答案。所以我们将原来的区间一分为2,设最优点是a,那么就分为[l,a-1]和[a+1,r]两段区间,继续重复上面一段的操作,即RMQ找出最小值,然后记录这些信息,重新仍回堆里面。
因此我们的struct要记录p(右端点的位置),l(当前区间左端点),r(区间右端点),sum(最大的区间和)
具体细节见代码:
#include<bits/stdc++.h>
#define MAXN 500005
#define ll long long
using namespace std;
int read(){
    char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
    x=x*10+c-'0';return x*y;
}
struct node{
    int num,p,l,r;
    bool operator<(const node x)const{
        return num<x.num;
    }
};
priority_queue<node> q;
int n,k,l,r,sum[MAXN],Min[MAXN][20];
ll ans;
int getmin(int x,int y){
    return sum[x]<sum[y]?x:y;
}
void pre(){
    for(int i=1;(1<<i)<=n;i++)
     for(int j=0;j+(1<<i)-1<=n;j++)
      Min[j][i]=getmin(Min[j][i-1],Min[j+(1<<i-1)][i-1]);
}
int query(int l,int r){
    if(l>r) return -1;
    int k=log2(r-l+1);
    return getmin(Min[l][k],Min[r-(1<<k)+1][k]);
}
int main()
{
    n=read();k=read();l=read();r=read();
    for(int i=1;i<=n;i++){
        int x=read();sum[i]=sum[i-1]+x;Min[i][0]=i;
    }
    pre();
    for(int i=l;i<=n;i++) q.push((node){sum[i]-sum[query(max(i-r,0),i-l)],i,max(i-r,0),i-l});
    while(k--){
        node e=q.top();q.pop();
        int p=query(e.l,e.r);ans+=e.num;
        int x1=query(e.l,p-1),x2=query(p+1,e.r);
        if(x1!=-1) q.push((node){sum[e.p]-sum[x1],e.p,e.l,p-1});
        if(x2!=-1) q.push((node){sum[e.p]-sum[x2],e.p,p+1,e.r});
    }
    printf("%lld",ans);
    return 0;
}


Description

身为IOI金牌的gtyzs有自己的一个OJ,名曰GOJ。GOJ上的题目可谓是高质量而又经典,他在他的OJ里面定义了一个树形的分类目录,且两个相同级别的目录是不会重叠的。比如图论的大目录下可能分为最短路,最小生成树,网络流等低一级的分类目录,这些目录下可能还有更低一级的目录,以此类推。现在gtyzs接到了一个任务,要他为SDTSC出题。他准备在自己的OJ题库中找出M道题作为SDTSC的试题,而他深知SDTSC的童鞋们个个都是神犇,所以gtyzs认为自己出的这M道题中,每道题都应该属于不少于L种分类目录;可他又怕自己的题没有人会做,所以每道题也应该属于不超过R种分类目录,并且这些分类目录应该是连续的,不能出现断层。对了,最重要的是,不存在一道题,它属于两个分类目录且这两个目录不是包含关系。(比如不存在一道题它既是一道DP题,又是一道网络流题)gtyzs怕被骂,所以他希望不存在任意的一对题目(u,v),使得u所属的分类目录与v完全相同。举例来说,gtyzs不能出两道同样的都是动态规划,背包的题,但是却可以出一道属于动态规划,背包和01背包的题和一道属于背包,01背包的题,当然也可以出一个属于图论的题和一个属于数论的题。(注意:一道题所属的分类目录不一定从根开始,如加粗部分所示)
为了让自己的题目变得更加靠谱,他给每一个分类目录都定了一个靠谱值ai,这个值可正可负。一道题的靠谱度为其从属的分类目录靠谱值的加和。我们假设动态规划的靠谱值为10,插头DP的靠谱值为-5,则一道动态规划插头DP的题的靠谱值就是5。gtyzs希望自己出的这M道题,在满足上述前提条件下,靠谱度总和最大。gtyzs当然会做啦,于是你就看到了这个题。

Input

输入的第一行是一个正整数N,代表分类目录的总数。
接下来的一行,共N个正整数,第i个正整数为fi,表示分类目录i被fi所包含。保证一个分类目录只会被一个分类目录所包含,且包含关系不存在环。特别的,fi=0表示它是根节点,我们保证这样的点只存在一个。
接下来的一行,共N个整数,第i个数表示ai。
最后一行,三个正整数M,L,R。
对于100%的数据,1≤N≤500,000,1≤M≤500,000,|ai|≤2,000。保证输入数据有解。
为了方便,所有的测试数据中都有f1=0,且对于任意的i∈[2,N],有fi<i。
Output

一行一个整数,表示最大的靠谱度。

Sample Input

7

0 1 1 2 2 3 3

2 3 4 1 2 3 4

3 3 3
Sample Output

26
HINT

对于样例数据,gtyzs选择这样的3道题:T1属于分类目录1,3,7,T2属于分类目录1,3,6,T3属于分类目录1,2,5。这三道题是满足要求的能取得最大靠谱度26的试题 题解:JudgeOnline/upload/201604/Solution-4458.rar By jinlifu1999


这道题题面有点恶心,简单的说就是一棵树,让你选M条不同的链,满足链的长度大于等于L,小于等于R。并且链不能弯曲(即经过其父亲后只能往上走,不能走到父亲节点的另外一个儿子去),求最大的链的权值和。
这道题和超级钢琴很想,唯一的不同就是这道题上树了,上了树自然就难搞了许多,但是大致思路是一样的。需要注意的细节是:
1.树上RMQ是类似于LCA的一个操作,从大到小一直往上跳,边跳边记录RMQ的值。
2.这道题我的写法比较繁琐,因为说了要把区间分为[l,a-1]和[a+1,r],那么对于树来说,a+1还好办,就是 F [ a ] [ 0 ] F[a][0] F[a][0](也就是a的父亲),然而a-1这个东西就很妖艳了,因为我们不知道它哪个儿子与r在同一条链上,因此我的写法是求RMQ的时候,让下面的点走不到上面的点,即始终保持dep[r]>dep[l],那么这样我们查询(a,r),就刚好跳到a和r路径上的a的儿子就停止了。但这样做的副作用是求[l,a-1]的RMQ时就必须询问( F [ l ] [ 0 ] , F [ a ] [ 0 ] F[l][0],F[a][0] F[l][0],F[a][0]),这样才能保证走到l。
而且这样写还有一个副作用,就是有些点,它的sum[x]是要减去sum[0]的,但是这里无论怎么变,1的父亲就是0。而0又没有父亲,所以无论如何查询都到不了0,所以答案可能会偏小。所以我们要建一个虚点1,原来树上的点都后移一位,即原来的1~n变为2 ~n+1。这样一来,点1的父亲为0。而点1与0的实际意义是相同的,sum[x]-sum[1]和原来的减去sum[0]具有相同的意义。这样的操作就能保证有减0的情况。
#include<bits/stdc++.h>
#define MAXN 500005
#define ll long long
using namespace std;
int read(){
    char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
    x=x*10+c-'0';return x*y;
}
int n,cnt,k,l,r,head[MAXN],nxt[MAXN],go[MAXN],w[MAXN];
int dep[MAXN],sum[MAXN],F[MAXN][22],Min[MAXN][22];
ll ans;
struct node{
    int num,p,l,r;
    bool operator<(const node x)const{
        return num<x.num;
    }
};
priority_queue<node> q;
int getmin(int x,int y){
    return sum[x]<sum[y]?x:y;
}
void add(int x,int y){
    go[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
}
void dfs(int x,int fa){
    F[x][0]=fa;sum[x]=sum[fa]+w[x];Min[x][0]=getmin(x,fa);dep[x]=dep[fa]+1;
    for(int i=head[x];i!=-1;i=nxt[i])dfs(go[i],x);
}
void pre(){
    for(int j=1;(1<<j)<=n;j++)
     for(int i=2;i<=n+1;i++){
        F[i][j]=F[F[i][j-1]][j-1];
        Min[i][j]=getmin(Min[i][j-1],Min[F[i][j-1]][j-1]);
     }
}
int getfa(int x,int l){
    int res=0;
    for(int i=19;i>=0;i--)
      if(res+(1<<i)<=l){res+=(1<<i);x=F[x][i];}
    return x;
}
int query(int l,int r){
    int x=r,p=dep[l];
    if(dep[l]>=dep[r]) return -1;
    for(int i=19;i>=0;i--)
     if(dep[F[r][i]]>p){
        x=getmin(x,Min[r][i]);r=F[r][i];
     }
    return x;
}
int main()
{
    n=read();
    memset(head,-1,sizeof(head));
    for(int i=2;i<=n+1;i++){
        int x=read()+1;
        add(x,i);
    }
    for(int i=2;i<=n+1;i++)w[i]=read();
    k=read();l=read();r=read();if(l>r)swap(l,r);
    dfs(2,1);pre();dep[0]=-1;
    for(int i=2;i<=n+1;i++)
     if(dep[i]>=l){
        int fl=getfa(i,l),fr=F[getfa(i,r)][0];
        q.push((node){sum[i]-sum[query(fr,fl)],i,fr,fl});
     }
    while(k--){
        node e=q.top();q.pop();
        int p=query(e.l,e.r);ans+=e.num;
        int x1=query(e.l,F[p][0]);
        int x2=query(p,e.r);
        if(x1!=-1) q.push((node){sum[e.p]-sum[x1],e.p,e.l,F[p][0]});
        if(x2!=-1) q.push((node){sum[e.p]-sum[x2],e.p,p,e.r});
    }
    printf("%lld",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值