[NOI2010]超级钢琴「RMQ」
题目描述
小 Z 是一个小有名气的钢琴家,最近 C 博士送给了小 Z 一架超级钢琴,小 Z 希望能够用这架钢琴创作出世界上最美妙的音乐。
这架超级钢琴可以弹奏出 \(n\) 个音符,编号为 \(1\) 至 \(n\)。第 \(i\) 个音符的美妙度为 \(A_i\),其中 \(A_i\) 可正可负。
一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于 \(L\) 且不多于 \(R\)。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。
小 Z 决定创作一首由 \(k\) 个超级和弦组成的乐曲,为了使得乐曲更加动听,小 Z 要求该乐曲由 \(k\) 个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小 Z 想知道他能够创作出来的乐曲美妙度最大值是多少。
输入格式
输入第一行包含四个正整数 \(n, k, L, R\)。其中 \(n\) 为音符的个数,\(k\) 为乐曲所包含的超级和弦个数,\(L\) 和 \(R\) 分别是超级和弦所包含音符个数的下限和上限。
接下来 \(n\) 行,每行包含一个整数 \(A_i\),表示按编号从小到大每个音符的美妙度。
输出格式
输出只有一个整数,表示乐曲美妙度的最大值。
输入输出样例
输入 #1
4 3 2 3
3
2
-6
8
输出 #1
11
说明/提示
样例解释
共有 \(5\) 种不同的超级和弦:
1.音符 \(1∼2\),美妙度为 \(3+2=5\);
2.音符 \(2∼3\),美妙度为 \(2+(−6)=−4\);
3.音符 \(3∼4\),美妙度为 \((−6)+8=2\);
4.音符 \(1∼3\),美妙度为 \(3+2+(−6)=−1\);
5.音符 \(2∼4\),美妙度为 \(2+(−6)+8=4\)。
最优方案为:乐曲由和弦 \(1,3,5\) 组成,美妙度为 \(5+2+4=11\)。
所有数据满足:\(−1000≤A_i≤1000\),\(1≤L≤R≤n\) 且保证一定存在满足要求的乐曲。
思路分析
打完 \(ST\) 表板子以后一下子崩到这个题,两眼瞪着发傻,不过确实是一道很不错的题
- 首先理解题意,是让我们求多个不同(但可以有重叠)的区间累加的最值问题,貌似和平时的 \(RMQ\) 问题所求的单个区间有些不同
- 一步一步来,既然是连续的区间,所以我们要首先想到前缀和优化,方便求出每个区间的美妙度
- \(RMQ\) 的常规做法是用 \(ST\) 表,而 \(ST\) 表的优点就在于可以将所有的区间利用倍增全部预处理出来从而达到 \(O(1)\)的查询,解决可重复贡献问题是非常好用的,所以就不必为此题多个区间累加的问题发愁
- 另一个关键点在于,这个区间的长度不是固定的,而是在一个区间的区间范围内
(禁止套娃)挑选一个区间,为了不落下一个区间,我们固定区间左端点,然后在要求范围内拓展区间长度,在这个范围内进一步找到最优的那一个右端点即可,这样就可以构造一个四元组 \((o,l,r,k)\),\(o\) 表示左端点,\(l,r\) 表示右端点的区间,\(k\) 表示最优右端点的位置 - 接下来将这个四元组压进优先队列即可,每次取出最大的就行,很显然的贪心
- 等等等等,好像漏了什么东西,假如我们将\((o,l,r,k)\)这个作为答案选出以后,对于 \((o,l,r)\),右端点很有可能在 \((l,r)\) 之间存在一个仅次于 \(k\) 但优于其他答案的更优解,所以我们在每次选出一个累加进答案以后还要将其分开,因为在 \(k\) 的左边和右边都可能有一个位置有着成为最优解的机会
细节见代码
Code
#include
#include
#include
#include
#include
#define N 500010
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int inf = 0x3f3f3f3f;
int n,l,r,k,sum[N],mx[N][22],id[N][22],logn[N];
long long ans;
struct node{ //结构体存四元组
int o,l,r,k;
node(){}
node(int _o,int _l,int _r,int _k){ //构造函数,压入堆里更方便些
o = _o,l = _l,r = _r,k = _k;
}
bool operator <(const node &_node)const{ //重载运算符,美妙度大的优先
return sum[k]-sum[o-1] < sum[_node.k]-sum[_node.o-1];
}
};
priority_queueq;
void ST(){ //ST表预处理
for(int j = 1;j <= 21;j++){
for(int i = 1;i <= n;i++){
if(i+(1<mx[i+(1<<(j-1))][j-1]){
mx[i][j]=mx[i][j-1];
id[i][j]=id[i][j-1];
}else{
mx[i][j]=mx[i+(1<<(j-1))][j-1];
id[i][j]=id[i+(1<<(j-1))][j-1];
}
}
}
}
}
int RMQ(int l,int r){ //找出区间最值,并返回对应位置
int s = logn[r-l+1],ID;
if(mx[l][s]>mx[r-(1<=3)logn[i] = logn[i>>1]+1;
}
ST();
for(int i = 1;i <= n-l+1;i++)q.push(node(i,i+l-1,min(i+r-1,n),RMQ(i+l-1,min(i+r-1,n))));//全都压进去先
for(int i = 1;i <= k;i++){
node t = q.top();q.pop();
ans+=sum[t.k]-sum[t.o-1];
if(t.k>t.l)q.push(node(t.o,t.l,t.k-1,RMQ(t.l,t.k-1))); //分裂操作
if(t.k