[NOI2010]超级钢琴 题解

NOI超级钢琴

题意:给定序列,求前 k k k 大长度为 [ L , R ] [L,R] [L,R] 的子段和的和

  • 我们将子段和求出来之后,取前 k k k 大即为答案所求,可以用堆维护

  • 问题在于如何求子段和

  • i i i 为左端点,那么右端点一定在 [ i + L − 1 ,   i + R − 1 ] [i+L-1,\space i+R-1] [i+L1, i+R1]

  • 若我们要求使得以 i i i 为左端点子段和最大的右端点 x x x,即求最大的 s u m ( x ) − s u m ( i − 1 ) sum(x)-sum(i-1) sum(x)sum(i1) s u m sum sum 表示前缀和),相当于求最大的 s u m ( x ) sum(x) sum(x) ,即求区间 [ i + L − 1 ,   i + R − 1 ] [i+L-1,\space i+R-1] [i+L1, i+R1] 最大的 s u m sum sum

    定义 q u e r y   (   l e f t ,   r i g h t   ) query\space (\space left,\space right\space ) query ( left, right ) 右端点在 [ l e f t , r i g h t ] [left,right] [left,right] 中,求使 s u m sum sum 最大的位置 x x x [ i , x ] [i,x] [i,x] 产生的贡献即为 s u m ( x ) − s u m ( i − 1 ) sum(x)-sum(i-1) sum(x)sum(i1)

  • 而仅仅维护最大的是不够的,我们最终需要前 k k k 大,因此我们需要将所有可能对答案有贡献的子段都维护出来,直到满足数量 k k k

[NOI2010]超级钢琴 题解_第1张图片

如图,在求出 x x x 之后,根据 x x x 分裂成两个区间,这两个区间的最大子段位置 x 1 = q u e r y ( l e f t , x − 1 ) x1=query(left,x-1) x1=query(left,x1) 以及 x 2 = q u e r y ( x + 1 , r i g h t ) x2=query(x+1,right) x2=query(x+1,right) 子段 [ i , x 1 ] [i,x1] [i,x1] [ i , x 2 ] [i,x2] [i,x2] 可能会对答案产生贡献(相当于求以 i i i 为左端点的次大以及次次大,两者一定小于 [ i , x ] [i,x] [i,x] 的子段产生的贡献)

如何维护

  • 我们发现将一个区间分裂成两个区间需要知道该区间的 l e f t left left r i g h t right right,以及最大位置 x x x。 同时,为了维护某一位置产生的值 v a l = ( s u m ( x ) − s u m ( i − 1 ) ) val=(sum(x)-sum(i-1)) val=(sum(x)sum(i1)),还需要知道区间左端点 i i i
  • 因此对于一个区间,我们维护他的 i , l e f t , r i g h t , x , v a l i,left,right,x,val i,left,right,x,val

整体做法

  • 先枚举 i i i 1 1 1 n n n 所有作为左端点,求出最大位置,放入堆中
  • 在堆中进行 k k k 轮操作,每次取最大值,将其踢出堆,同时加入两个分裂的区间
#include
using namespace std;
const int MAXN=5e5+5;
int n,k,L,R,sum[MAXN];
int st[MAXN][20];
void build()
{
	for(int i=1;i<=n;i++) st[i][0]=i;
	for(int j=1;j<=log2(n);j++)
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			int x=st[i][j-1], y=st[i+(1<<(j-1))][j-1];
			st[i][j]= (sum[x]>sum[y]) ? x : y;
		}
	}
}
int query(int l,int r)
{
	int t=log2(r-l+1);
	int x=st[l][t], y=st[r-(1<<t)+1][t];
	return (sum[x]>sum[y]) ? x :y; 
}
struct Node{
	int id,left,right,pos,val;
	bool operator < (const Node &x)const{
		return x.val>val;
	}
};
priority_queue <Node> q;
int main()
{
	cin>>n>>k>>L>>R;
	for(int i=1;i<=n;i++)
		cin>>sum[i], sum[i]+=sum[i-1];
	build();
	for(int i=1;i+L-1<=n;i++)
	{
		int left=i+L-1, right=min(n,i+R-1), x=query(left,right);
		q.push((Node){i,left,right,x,sum[x]-sum[i-1]});
	}
	long long ans=0;
	while(k--)
	{
		Node hd=q.top(); q.pop();
		ans+=hd.val;
		if(hd.pos>hd.left)
		{
			int i=hd.id, left=hd.left, right=hd.pos-1, x=query(left,right);
			q.push((Node){i,left,right,x,sum[x]-sum[i-1]});
		}
		if(hd.pos<hd.right)
		{
			int i=hd.id, left=hd.pos+1, right=hd.right, x=query(left,right);
			q.push((Node){i,left,right,x,sum[x]-sum[i-1]});
		}
	}
	cout<<ans;
	return 0;
}
 

你可能感兴趣的:(题解,c++)