BZOJ2006【NOI2010】超级钢琴题解(贪心+堆+ST表)

题目:BZOJ2006.
题目大意:给定一个长度为 n n n的序列 a a a,现在要求找 k k k个满足长度在 [ l , r ] [l,r] [l,r]内的子段,输出最大的子段和之和.
1 ≤ n , k ≤ 5 ∗ 1 0 5 , 1 ≤ L ≤ R ≤ n , − 1 0 3 ≤ a i ≤ 1 0 3 1\leq n,k\leq 5*10^5,1\leq L\leq R\leq n,-10^3\leq a_i\leq 10^3 1n,k5105,1LRn,103ai103.

首先,肯定是取最大的 k k k个子段之和最优.

考虑 k = 1 k=1 k=1时,很容易想到枚举每一个子段的左端点,并找到使得子段和最大的右端点求出和,在这些和中取最大就是答案,找最大可以用前缀和+ST表实现.

考虑 k = 2 k=2 k=2时,显然在 k = 1 k=1 k=1时取完一个子段 [ i , p ] [i,p] [i,p]后,还有一个次大的必然只有两种情况:
1.在剩下的 k = 1 k=1 k=1的候选答案中选取.
2.左端点为 i i i,右端点在 [ i + l − 1 , p − 1 ] [i+l-1,p-1] [i+l1,p1] [ p + 1 , i + r − 1 ] [p+1,i+r-1] [p+1,i+r1].

显然我们可以分别把第二种情况的右端点两种情况的最大值分别放入候选集合,然后取最大.

k > 2 k>2 k>2时,我们可以类比这种处理方法,维护一个候选集合,每次取完一个后删掉,并放入删掉的答案会产生的另外两个候选答案.

显然候选集合可以用堆来维护,候选集合大小不会超过 O ( n + k ) O(n+k) O(n+k).具体来说可以维护一个五元组 ( s t , l , r , m x , p ) (st,l,r,mx,p) (st,l,r,mx,p)表示左端点为 s t st st,右端点在区间 [ l , r ] [l,r] [l,r]中,最大值为 m x mx mx,且此时右端点为 p p p.

每次从候选集合中删去一个五元组 ( s t , l , r , m x , p ) (st,l,r,mx,p) (st,l,r,mx,p),就可以放入另外两个五元组 ( s t , l , p − 1 , m x 1 , p 1 ) (st,l,p-1,mx_1,p_1) (st,l,p1,mx1,p1) ( s t , p + 1 , r , m x 2 , p 2 ) (st,p+1,r,mx_2,p_2) (st,p+1,r,mx2,p2).

时间复杂度 O ( ( n + k ) log ⁡ n ) O((n+k)\log n) O((n+k)logn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000,C=20;

int n,m,l,r,a[N+9];
int mx[N+9][C],p[N+9][C],lg[N+9];

void Pre_doubly(){
  for (int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
  for (int i=1;i<=n;++i) mx[i][0]=a[i],p[i][0]=i;
  for (int i=1;i<C;++i){
    int t=1<<i-1;
    for (int j=1;j+t<=n;++j)
      if (mx[j][i-1]>=mx[j+t][i-1])
        mx[j][i]=mx[j][i-1],p[j][i]=p[j][i-1];
      else mx[j][i]=mx[j+t][i-1],p[j][i]=p[j+t][i-1];
  }
}

int Query_p(int L,int R){
  int t=lg[R-L+1];
  return mx[L][t]>=mx[R-(1<<t)+1][t]?p[L][t]:p[R-(1<<t)+1][t];
}

struct state{
  int st,l,r,mx,p;
  state(int St=0,int L=0,int R=0,int Mx=0,int P=0){st=St;l=L;r=R;mx=Mx;p=P;}
  bool operator < (const state &p)const{return mx<p.mx;}
};
priority_queue<state>q;
LL ans;

Abigail into(){
  scanf("%d%d%d%d",&n,&m,&l,&r);
  for (int i=1;i<=n;++i){
    scanf("%d",&a[i]);
    a[i]+=a[i-1];
  }
}

Abigail work(){
  Pre_doubly();
  for (int i=1;i+l-1<=n;++i){
  	int tl=i+l-1,tr=min(i+r-1,n),tp=Query_p(tl,tr);
  	q.push(state(i,tl,tr,a[tp]-a[i-1],tp));
  }
  for (int i=1;i<=m;++i){
  	state t=q.top();q.pop();
  	ans+=(LL)t.mx;
    if (t.p^t.l){
  	  int tp=Query_p(t.l,t.p-1);
      q.push(state(t.st,t.l,t.p-1,a[tp]-a[t.st-1],tp));
    }
    if (t.p^t.r){
  	  int tp=Query_p(t.p+1,t.r);
  	  q.push(state(t.st,t.p+1,t.r,a[tp]-a[t.st-1],tp));
    }
  }
}

Abigail outo(){
  printf("%lld\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值