P2048 [NOI2010] 超级钢琴
题目
小 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 ,表示按编号从小到大每个音符的美妙度。
输出格式
输出只有一个整数,表示乐曲美妙度的最大值。
输入输出样例
输入样例
4 3 2 3
3
2
-6
8
输出样例
11
题解
显然易见,如果用暴力的方法会超时。 但是当我们每次遍历数组A时,每次都会从 [ i , i + L - 1] ~ [ i , i + R - 1] 之间选取最优的区间的最优值 (求和最大区间的和 ) 。所以我们每次可以固定i值,也就是左值。
为了方便期间,我们可以构建一个五元组 Node<o,l,t,r,data>
- o代表固定的左值
- l = o + L - 1;区间右端点的最小值
- r = o +R - 1; 区间右端点的最大值
- t 区间右端点的最优取值
- data = sum[ t ] - sum[ l - 1 ] 最优的区间的最优值 (求和最大区间的和 )
class Node{
public:
int o,l,r,t;
ll data;
Node(){}
Node(int o,int l,int r){
this->o = o;
this->l = l;
this->r = r;
this->t = query(l,r);//寻找[i,i+L-1]~[i,i+R-1]的最优解区间的右端点位置
this->data = a[t] - a[o-1];
}
//这里重载了<运算符是为了下面使用优先队列优化
friend bool operator < (const Node& o1, const Node& o2) {
return a[o1.t] - a[o1.o - 1] < a[o2.t] - a[o2.o - 1];
}
};
问题来了如何快速求出区间里的和最大的一组子区间
- 首先子区间求和我们可以想到前缀和数组,结合ST表进行
O(1)
查询。
这里的ST算法与普通的ST算法有一点差别,普通的ST算法不用求解最优解的位置,而这里我们需要求解最优值的位置也就是t
/* ST表:查询最值的位置模板*/
#include<bits/stdc++.h>
#include<cmath>
const int N = 500005;
int n;
int a[N];
int smax[N][20];
int smin[N][20];
//ST表初始化
void ST() {
for (int i = 1; i <= n; i++) {
smax[i][0] = i;
smin[i][0] = i;
}
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
int x = smax[i][j - 1], y = smax[i + (1 << (j - 1))][j - 1];
smax[i][j] = a[x] > a[y] ? x : y;
smin[i][j] = a[x] < a[y] ? x : y;
}
}
//求最大值的位置
int queryMax(int l, int r) {
int k = log2(r - l + 1);
int x = smax[l][k], y = smax[r - (1 << k) + 1][k];
return a[x] > a[y] ? x : y;
}
//求最小值的位置
int queryMin(int l, int r) {
int k = log2(r - l + 1);
int x = smin[l][k], y = smin[r - (1 << k) + 1][k];
return a[x] < a[y] ? x : y;
}
但是新的问题又来了,怎么样才能找到K个呢?
这里我们可以用优先队列来优化这个问题:
-
首先我们可以用优先队列将大范围的最优值找出来,
大范围的最优解一定要比小范围的最优解还要优,因为大范围的最优解是所有小范围的最优解的最优解
-
然后在用贪心的思想
这里我觉得类似于bfs,但是比bfs更智能
将小范围的最优解搜索出来
/*Node 是之前我们规定的五元组,里面存的是o,l,t,r,data*/
// 这里我们优先队列先存的是我们找到的大范围的最优解
priority_queue<Node> prq;
for (int i = 1; i <= n; ++i) {
if(i+L-1<=n) {
//我只规定了用o,l,r来创建Node类,因为r和data可以用这三个求出来
Node no(i, i + L - 1, min(i + R - 1, n));
prq.push(no);
}
}
// 这里我们用了类似bfs的搜索方法搜到了k个最优解
while (k--){
Node no = prq.top();
int o = no.o; int r = no.r; int l = no.l; int t = no.t;
ans += no.data;
prq.pop();
//这里的搜索比传统的bfs更加智能,因为这里是优先队列所以队首元素不一定是当前存放的节点,也有可能是之前的节点
if(t != l) {
Node nn(o,l,t-1);
prq.push(nn);
}
if(t != r){
Node nn(o,t+1,r);
prq.push(nn);
}
}
代码
#include<bits/stdc++.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int n;
const int N = 500005;
ll a[N];
ll smax[N][20];
void ST() {
for (int i = 1; i <= n; i++) smax[i][0] = i;
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
int x = smax[i][j - 1], y = smax[i + (1 << (j - 1))][j - 1];
smax[i][j] = a[x] > a[y] ? x : y;
}
}
int query(int l, int r) {
int k = log2(r - l + 1);
int x = smax[l][k], y = smax[r - (1 << k) + 1][k];
return a[x] > a[y] ? x : y;
}
class Node{
public:
int o,l,r,t;
ll data;
Node(){}
Node(int o,int l,int r){
this->o = o;
this->l = l;
this->r = r;
this->t = query(l,r);
this->data = a[t] - a[o-1];
}
friend bool operator < (const Node& o1, const Node& o2) {
return a[o1.t] - a[o1.o - 1] < a[o2.t] - a[o2.o - 1];
}
};
int main(){
int k,L,R;
scanf("%d%d%d%d",&n,&k,&L,&R);
a[0] = 0;
for (int i = 1; i <= n; ++i) {
scanf("%lld",&a[i]);
a[i] += a[i-1];
}
ST();
priority_queue<Node> prq;
for (int i = 1; i <= n; ++i) {
if(i+L-1<=n) {
Node no(i, i + L - 1, min(i + R - 1, n));
prq.push(no);
//cout<<i<<" "<<no.t<<" "<<no.data<<endl;
}
}
ll ans = 0;
while (k--){
Node no = prq.top();
int o = no.o; int r = no.r; int l = no.l; int t = no.t;
ans += no.data;
prq.pop();
if(t != l) {
Node nn(o,l,t-1);
prq.push(nn);
}
if(t != r){
Node nn(o,t+1,r);
prq.push(nn);
}
}
printf("%lld",ans);
return 0;
}