题解 P2048 【[NOI2010]超级钢琴】

题意简述:

1.给你一个序列 $n$,求出每一个长度为 l~r 的子序列数值的和。

2.取出前 $k$ 大个,累及总和。

------------

Solution:

容易想到,求静态子序列之和,可以使用前缀和,区间 l~r 和即为 $pre[r]-pre[l-1]$。

于是可以暴力求出每个的和然后排序累加,但这样复杂度过高,会 TLE。

再想想,对于每一个超级和弦的起始点 k,终点的范围在 k+l-1~k+r-1 之间。

那么我们若能找出每一个 k 中,对应的终点中的最大值,不断取出最大值不就可以了吗?

显然是不可行的。

理由1:k 可能大于 $n$,即取完不够。

理由2:如图:

当 1~3,2~3 取完时,1~2 显然相对于其他解更优。

所以,当我们累加一个节点 mid 时,应考虑 l~mid-1,mid+1~r,将其加入候选队列,以避免漏掉更优解。

由此可见,我们需要维护 1.前缀和 2.RMQ 3.当前最优解(优先队列)。

同时,优先队列中维护五个元素:当前选取节点编号,当前的范围 l~r,当前的最大值,当前的超级和弦的来源。

注意:st 表维护的是前缀和最大值,优先队列则是区间和最大值。

我的写法貌似和其他人不大一样(跑得慢)代码内有注释:

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

struct hx
{
	long long now,lk,rk,val,pos;//当前选取编号,l~r,max,和弦来源
	hx();
	hx(long long _now,long long _lk,long long _rk,long long _val,long long _pos):now(_now),lk(_lk),rk(_rk),val(_val),pos(_pos){};
	bool operator < (hx x) const
	{
        return val<x.val;
    }
};//和弦结构体,并重载<,按值排序
priority_queue<hx> q;
long long n,k,l,r,st[23][2000500][2],hxt[2000500],ans;//st表,第三维0是维护区间前缀和最大值,1是区间前缀和最大值编号
//一开始没开long long错了,一气之下想都没想全开成long long
void st_pre()
{
	for(int i=1;i<=20;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(st[i-1][j][0]<st[i-1][j+(1<<(i-1))][0]) st[i][j][0]=st[i-1][j+(1<<(i-1))][0],st[i][j][1]=st[i-1][j+(1<<(i-1))][1];
			else st[i][j][0]=st[i-1][j][0],st[i][j][1]=st[i-1][j][1];
		}//在更新区间前缀和最大值时同时更新最大值位置(稍微改下模板)
	}
}//st表预处理

int main()
{
	scanf("%lld %lld %lld %lld",&n,&k,&l,&r);//读入
	for(int i=1;i<=n;i++) scanf("%lld",&st[0][i][0]),st[0][i][0]+=st[0][i-1][0],st[0][i][1]=i;//维护初始前缀和最大值与初始位置
	st_pre();
	for(int i=1;i<=20;i++) hxt[1<<i]++;
	for(int i=1;i<=n;i++) hxt[i]+=hxt[i-1];//O(1)询问预处理
	for(int i=1;i<=n-l+1;i++)
	{
		long long ll=i+l-1,rr=min(n,i+r-1),len=rr-ll+1;//左,右,长度,注意范围取min
		long long max1=st[hxt[len]][ll][0],max2=st[hxt[len]][rr-(1<<hxt[len])+1][0],id1=st[hxt[len]][ll][1],id2=st[hxt[len]][rr-(1<<hxt[len])+1][1];//这四个变量其实就是st表中两段区间内的前缀和最大值与编号,取max
		if(max1<max2) q.push(hx(id2,ll,rr,max2-st[0][i-1][0],i));
		else q.push(hx(id1,ll,rr,max1-st[0][i-1][0],i));//插入大的,同时用前缀和最大值减去起始点-1,得到序列和
	}
	int js=0;
	while(js<k)
	{
		js++;
		ans=ans+q.top().val;//更新
		long long ll=q.top().lk,rr=q.top().rk,mid=q.top().now,poss=q.top().pos;//左端点,右端点,上次取的位置,原本起点
		if(mid>ll)//左边有可更新插入的
		{
			mid--;//因为不能重复取mid,故减一
			long long len=mid-ll+1,max1=st[hxt[len]][ll][0],max2=st[hxt[len]][mid-(1<<hxt[len])+1][0],id1=st[hxt[len]][ll][1],id2=st[hxt[len]][mid-(1<<hxt[len])+1][1];
			if(max1<max2) q.push(hx(id2,ll,mid,max2-st[0][poss-1][0],poss));
			else q.push(hx(id1,ll,mid,max1-st[0][poss-1][0],poss));//插入堆
			mid++;//一定要记得回来,下面可能还用到
		}
		if(rr>mid)//同上,右边情况
		{
			mid++;
			long long len=rr-mid+1,max1=st[hxt[len]][mid][0],max2=st[hxt[len]][rr-(1<<hxt[len])+1][0],id1=st[hxt[len]][mid][1],id2=st[hxt[len]][rr-(1<<hxt[len])+1][1];
			if(max1<max2) q.push(hx(id2,mid,rr,max2-st[0][poss-1][0],poss));
			else q.push(hx(id1,mid,rr,max1-st[0][poss-1][0],poss));
			mid--;
		}
		q.pop();
	}
	printf("%lld",ans);//输出结果
	return 0;
}

题解清楚么?

return 0;

猜你喜欢

转载自www.cnblogs.com/wxg-Jay/p/12239549.html
今日推荐