Evanyou Blog 彩带

洛谷P2048 [NOI2010]超级钢琴 题解

2019/11/14 更新日志:

近期发现这篇题解有点烂,更新一下,删繁就简,详细重点。代码多加了注释。就酱紫啦!


正解步骤

  1. 我们需要先算美妙度的前缀和,并初始化RMQ。

  2. 循环 \(i\)\(1\)\(n\) ,因为以i为起点的 和弦 终点必定是 \(i + L - 1\)\(i + R - 1\) 之间,所以只要在区间内用RMQ取 超级和弦 ,并加入以美妙度从小排到大的优先队列中。

  3. 取出堆顶元素,将美妙度加入 \(ans\) ,并将元素切为从 (当前元素的左边界 到 当前元素终点 - 1) 和 (\(当前元素终点 + 1 到 当前元素右边界\)) 两个部分,并再次加入优先队列,依次进行 \(k\) 次。

  4. 输出答案即可


为什么要使用前缀和 and RMQ?

数据范围是500000,很明显,为了优化需要前缀和 and RMQ。

前缀和最明显的用处,是可以优化一个用来循环累加和的 \(n\) 。而 \(RMQ\) ,显然区间最值符合题目要求。

这两个算法询问答案都是O(1)。前缀和后面减去前面,RMQ只需要初始化一下,然后O(1)询问即可。


为什么第三步要切开元素并放入优先队列? 直接累加前 \(k\) 个元素不行么?

首先,我们肯定可以确定:优先队列中第一大的和弦一定是 \(全局\) 最大的和弦。 不要问我怎么证明

那么优先队列中第二大的和弦一定是 \(全局\) 次大的和弦么?这就不一定了。

所以我们需要切开元素并放入优先队列,保证每次取出来的元素一定是全局大小排名的元素

自己拿出纸和笔,结合题解自己思考,在草稿纸上演算一下,就懂了


那我就把解题思路放上吧233

实在还有问题,私信本人233

感谢老师 @apple365 的思路指引。

AC代码

#include<bits/stdc++.h>
#include<cctype>
#pragma GCC optimize(2)

#define in(a) a = read()
#define out(a) write(a)
#define outn(a) out(a),putchar('\n')

#define ll long long
#define Min(a,b) a < b ? a : b
#define Max(a,b) a > b ? a : b
#define rg register
#define New ll

using namespace std;

namespace IO_Optimization{

	inline New read()
	{
	    New X = 0,w = 0;
		char ch = 0;

		while(!isdigit(ch))
		{
			w |= ch == '-';
			ch=getchar();
		}
	    while(isdigit(ch))
		{
			X = (X << 3) + (X << 1) + (ch ^ 48);
			ch = getchar();
		}
	    return w ? -X : X;
	}

	inline void write(New x)
	{
	     if(x < 0) putchar('-'),x = -x;
	     if(x > 9) write(x/10);
	     putchar(x % 10 + '0');
	}

	#undef New
}
using namespace IO_Optimization;//上面一坨优化的东西不用在意 

const int MAXN = 500000 + 2;//定义常亮 

int n,k,L,R;
int sum[MAXN],lg[MAXN],dp[MAXN][20],pos[MAXN][20];
//  前缀和    lg2值
//dp[i][j]表示i的2^j次方祖先  pos数组来记录最佳位置 
ll ans;
struct Node
{
	int start, left, right, t, val; 
	//超级和弦的起点 左、右边界 最值位置 最值 
	bool operator < (const Node &next) const
	{
		return val < next.val; //最值从大到小排序 
	} 
};

inline void RMQ_init() //预处理 
{ 
	for(rg int j = 1;j <= 20; ++j)
		for(rg int i = 1;i + (1 << j) - 1 <= n; ++i)
		{
			if(dp[i][j - 1] > dp[i + (1 << (j - 1))][j - 1])
			{
				dp[i][j] = dp[i][j - 1];
				pos[i][j] = pos[i][j - 1];//更新最值位置 
			}
			else
			{
				dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
				pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
			}
		}
	return;
} 

inline int RMQ_query(int l, int r) //返回最值的位置 
{
	int t, tmp = lg[r - l + 1];
	if(dp[l][tmp] > dp[r - (1 << tmp) + 1][tmp])
		t = pos[l][tmp];
	else t = pos[r - (1 << tmp) + 1][tmp];
	return t;
}

int main()
{
	in(n),in(k),in(L),in(R);
	lg[0] = -1;//lg2(0) = -1,方便后面预处理lg2值 
	for(rg int i = 1;i <= n; ++i)
	{
		int a = read();//读入音符 
		sum[i] = sum[i - 1] + a; //前缀和 
		lg[i] = lg[i >> 1] + 1; //预处理lg2值 
		dp[i][0] = sum[i];
		pos[i][0] = i; //初始化最大值的位置 
	}
	RMQ_init();//初始化 
	priority_queue<Node> pq; //定义优先队列 
	for(rg int i = 1;i + L - 1 <= n; ++i) //计算每个位置最大的超级和弦 
	{
		int t = RMQ_query(i + L - 1, Min(n, i + R - 1)); 
		Node cur;
		cur.val = sum[t] - sum[i - 1]; //由前缀和取最大值 
		cur.t = t;
		cur.start = i; //当前超级和弦的起始位置 
		cur.left = i + L - 1; //当前的左边界 
		cur.right = Min(n,i + R - 1); //当前的右边界 
		pq.push(cur); //入堆 
	}

	for(rg int i = 1;i <= k; ++i) //取k次堆顶的值 
	{
		Node cur = pq.top();
		pq.pop();
		ans = ans + cur.val; //累加结果 
		Node next;

		if(cur.t > cur.left) //当前取最值的位置 大于 当前和弦的 左边界 
		{
			next.start = cur.start;
			next.left = cur.left;
			next.right = cur.t - 1; //新的右边界 
			next.t = RMQ_query(next.left, next.right);
			next.val = sum[next.t] - sum[next.start - 1];
			pq.push(next);
		}

		if(cur.t < cur.right) //当前取最值的位置 小于 当前和弦的 右边界 
		{
			next.start = cur.start;
			next.left = cur.t + 1; //新的左边界 
			next.right = cur.right;
			next.t = RMQ_query(next.left, next.right);
			next.val = sum[next.t] - sum[next.start - 1];
			pq.push(next);
		}
	}
	outn(ans);
	return 0;
}

END.

posted @ 2019-11-14 21:09  御·Dragon  阅读(183)  评论(0编辑  收藏  举报



Contact with me