Description
小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。 这架超级钢琴可以弹奏出n个音符,编号为1至n。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。 小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最大值是多少。
Input
第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所包含音符个数的下限和上限。 接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。
Output
只有一个整数,表示乐曲美妙度的最大值。
Sample Input
4 3 2 3
3
2
-6
8
Sample Output
11
【样例说明】
共有5种不同的超级和弦:
音符1 ~ 2,美妙度为3 + 2 = 5
音符2 ~ 3,美妙度为2 + (-6) = -4
音符3 ~ 4,美妙度为(-6) + 8 = 2
音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
音符2 ~ 4,美妙度为2 + (-6) + 8 = 4
最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。
题目大意是要求所有长度介于[L, R]的区间的最大值的前K大的和。朴素算法显然超时。
可以用RMQ算法和堆进行优化。
定义一个三元组(L, R1, R2)为区间[L, R1], [L, R1+1],..., [L, R2]中,每个区间和的最大值的最大值。(当然这个最大值可以用RMQ算出。)
于是,把所有的(i, i+L-1, i+R-1)都加到堆中,每次取堆顶元素(由于取出后此三元组中其它元素可能成为新的最大值,所以要从最大值处断开,若取到最大值的位置为pos,那么再将三元组(i, pos+1, i+R-1)和(i,i+L-1, pos-1)),取出K次累加即可。
注意要使用long long类型。
Accode:
手写堆版:
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
using namespace std;
const char fi[] = "piano.in";
const char fo[] = "piano.out";
const int maxN = 500010;
const int maxL = 20;
typedef long long int64;
int f[maxN][maxL];
int64 sum[maxN];
inline int rmq_max(int, int);
struct Node
{
int L, R1, R2, pos;
int64 Sum;
Node() {}
Node(int L, int R1, int R2):
L(L), R1(R1), R2(R2)
{
int q = 0;
for (; 1 << q < R2 - R1 + 2; ++q);
--q;
pos = rmq_max(f[R1][q], f[R2 -
(1 << q) + 1][q]);
Sum = sum[pos] - sum[L - 1];
}
Node(const Node &b):
L(b.L), R1(b.R1), R2(b.R2),
Sum(b.Sum), pos(b.pos) {}
inline bool operator<(const Node &b)
{
return Sum < b.Sum;
}
};
Node hp[maxN << 1];
int n, K, L, R, top;
void init_file()
{
freopen(fi, "r", stdin);
freopen(fo, "w", stdout);
return;
}
inline int getint()
{
int res = 0, sgn = 1;
char tmp;
do tmp = getchar();
while (!isdigit(tmp) && tmp != '-');
if (tmp == '-')
{
sgn = 0;
tmp = getchar();
}
do res = (res << 3) + (res << 1) + tmp - '0';
//用位运算优化乘以10的计算。
while (isdigit(tmp = getchar()));
return sgn ? res : -res;
} //一种不错的读入整型的方式。(注意符号判断。)
inline void ad_down(int i)
{
while ((i <<= 1) <= top)
{
if (i < top && hp[i] < hp[i + 1]) ++i;
if (hp[i >> 1] < hp[i])
swap(hp[i >> 1], hp[i]);
else break;
}
return;
}
inline void ad_up(int i)
{
for (; i >> 1; i >>= 1)
if (hp[i >> 1] < hp[i])
swap(hp[i >> 1], hp[i]);
else break;
return;
}
void readdata()
{
n = getint();
K = getint();
L = getint();
R = getint();
for (int i = 1; i < n + 1; ++i)
{
(sum[i] = getint()) += sum[i - 1];
f[i][0] = i;
}
return;
}
inline int rmq_max(int a, int b)
{
return sum[a] > sum[b] ? a : b;
}
void rmq()
{
for (int q = 0; 1 << q < n; ++q)
for (int i = 1; i + (1 << q) < n + 2; ++i)
f[i][q + 1] = rmq_max(f[i + (1 << q)][q],
f[i][q]);
}
inline void insert(int L, int R1, int R2)
{
hp[++top] = Node(L, R1, R2);
ad_up(top);
return;
}
inline void pop()
{
swap(hp[1], hp[top--]);
ad_down(1);
return;
}
void work()
{
rmq();
for (int i = 1; i + L < n + 2; ++i)
insert(i, i + L - 1, min(i + R - 1, n));
int64 ans = 0;
for (; K; --K)
{
Node tmp = hp[1];
pop();
ans += tmp.Sum;
if (tmp.pos > tmp.R1)
insert(tmp.L, tmp.R1, tmp.pos - 1);
if (tmp.pos < tmp.R2)
insert(tmp.L, tmp.pos + 1, tmp.R2);
}
printf("%I64d\n", ans);
return;
}
int main()
{
init_file();
readdata();
work();
return 0;
}
priority_queue版:
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <queue>
using namespace std;
const char fi[] = "piano.in";
const char fo[] = "piano.out";
const int maxN = 500010;
const int maxL = 20;
typedef long long int64;
int f[maxN][maxL];
int64 sum[maxN];
inline int rmq_max(int a, int b)
{
return sum[a] > sum[b] ? a : b;
}
struct Node
{
int L, R1, R2, pos;
int64 Sum;
Node() {}
Node(int L, int R1, int R2):
L(L), R1(R1), R2(R2)
{
int q = 0;
for (; 1 << q < R2 - R1 + 2; ++q);
--q;
pos = rmq_max(f[R1][q], f[R2 -
(1 << q) + 1][q]);
Sum = sum[pos] - sum[L - 1];
}
Node(const Node &b):
L(b.L), R1(b.R1), R2(b.R2),
Sum(b.Sum), pos(b.pos) {}
inline bool operator<(const Node &b) const
{return Sum < b.Sum;}
};
priority_queue <Node> hp;
int n, K, L, R, top;
void init_file()
{
freopen(fi, "r", stdin);
freopen(fo, "w", stdout);
return;
}
void readdata()
{
scanf("%d%d%d%d", &n, &K, &L, &R);
for (int i = 1; i < n + 1; ++i)
{
int tmp;
scanf("%d", &tmp);
sum[i] = tmp + sum[i - 1];
f[i][0] = i;
}
return;
}
void rmq()
{
for (int q = 0; 1 << q < n; ++q)
for (int i = 1; i + (1 << q) < n + 2; ++i)
f[i][q + 1] = rmq_max(f[i + (1 << q)][q],
f[i][q]);
}
void work()
{
rmq();
for (int i = 1; i + L < n + 2; ++i)
hp.push(Node(i, i + L - 1, min(i + R - 1, n)));
int64 ans = 0;
for (; K; --K)
{
Node tmp = hp.top();
hp.pop();
ans += tmp.Sum;
if (tmp.pos > tmp.R1)
hp.push(Node(tmp.L, tmp.R1, tmp.pos - 1));
if (tmp.pos < tmp.R2)
hp.push(Node(tmp.L, tmp.pos + 1, tmp.R2));
}
printf("%I64d\n", ans);
return;
}
int main()
{
init_file();
readdata();
work();
return 0;
}
第二次做:
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#define max(a, b) ((sum[a]) > (sum[b]) ? (a) : (b))
typedef long long int64; //
const int maxN = 500010;
int64 sum[maxN], f[maxN][20], ans;
struct Node
{
int L, R1, R2, pos;
int64 Max;
Node() {}
Node(int L, int R1, int R2):
L(L), R1(R1), R2(R2)
{
int len = 0;
for (; 1 << len <= R2 - R1 + 1; ++len);
--len;
pos = max(f[R1][len], f[R2 + 1
- (1 << len)][len]);
Max = sum[pos] - sum[L - 1];
}
} hp[maxN << 1];
int n, K, top, L, R;
inline int getint()
{
int res = 0; char tmp; bool sgn = 1;
do tmp = getchar();
while (!isdigit(tmp) && tmp != '-');
if (tmp == '-') {tmp = getchar(); sgn = 0;}
do res = (res << 3) + (res << 1) + tmp - '0';
while (isdigit(tmp = getchar()));
return sgn ? res : -res;
}
inline void ad_down()
{
for (int i = 1; (i <<= 1) <= top;)
{
if (i < top && hp[i].Max < hp[i + 1].Max) ++i;
if (hp[i >> 1].Max < hp[i].Max)
std::swap(hp[i], hp[i >> 1]);
else break;
}
return;
}
inline void ad_up()
{
for (int i = top; i >> 1; i >>= 1)
{
if (hp[i >> 1].Max < hp[i].Max)
std::swap(hp[i], hp[i >> 1]);
else break;
}
return;
}
int main()
{
freopen("piano.in", "r", stdin);
freopen("piano.out", "w", stdout);
n = getint(); K = getint();
L = getint(); R = getint();
for (int i = 1; i < n + 1; ++i)
(sum[f[i][0] = i] = getint()) += sum[i - 1];
for (int k = 1; 1 << k <= n; ++k)
for (int i = 0; i + (1 << k) < n + 2; ++i)
f[i][k] = max(f[i][k - 1], f[i +
(1 << (k - 1))][k - 1]);
for (int i = 1; i + L < n + 2; ++i)
{
hp[++top] = Node(i, i + L - 1, (i + R - 1)
< n ? (i + R - 1) : n);
ad_up();
}
for (; K; --K)
{
Node ths = hp[1]; hp[1] = hp[top--]; ad_down();
ans += ths.Max;
if (ths.R1 < ths.pos)
{
hp[++top] = Node(ths.L, ths.R1, ths.pos - 1);
ad_up();
}
if (ths.pos < ths.R2)
{
hp[++top] = Node(ths.L, ths.pos + 1, ths.R2);
ad_up();
}
}
printf("%I64d\n", ans);
return 0;
}
此题还可以用划分树解决。
定义四元组(L, R1, R2, rem)为区间[L, R1], [L, R1 + 1], ..., [L, R2]中每个区间和的第rem小的值。
那么思路同上,先找各个四元组中第(R2 - R1 + 1)小的数,再找第(R - R1)小的数,...,直到不能找为止。仍然是用用个堆维护所有的这样的四元组,每次取最大,取出K次即可。
Accode:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
const int maxN = 500010;
int tr[20][maxN], sumL[20][maxN], sum[maxN], ord[maxN], n, K, L, R;
int query(int l, int r, int L, int R, int D, int k)
{
if (l == r) return sum[ord[tr[D][l]]];
int _L = (L > l) ? sumL[D][L - 1] : 0,
_R = sumL[D][R], Mid = ((l + r) >> 1) + 1;
if (_R - _L >= k)
return query(l, Mid - 1, l + _L, l + _R - 1, D + 1, k);
else return query(Mid, r, Mid + L - l - _L,
Mid + R - l - _R, D + 1, k - _R + _L);
}
struct Node
{
int L, R1, R2, val, rem; Node() {}
Node(int L, int R1, int R2, int _rem):
L(L), R1(R1), R2(R2), rem(_rem),
val(query(1, n, R1, R2, 0, _rem) - sum[L - 1]) {}
Node(int L, int R1, int R2):
L(L), R1(R1), R2(R2), rem(R2 - R1 + 1),
val(query(1, n, R1, R2, 0, R2 - R1 + 1) - sum[L - 1]) {}
bool operator<(const Node &b) const
{return val < b.val;}
}; std::priority_queue <Node> hp; long long ans;
inline int getint()
{
int res = 0; char tmp; bool sgn = 1;
do tmp = getchar();
while (!isdigit(tmp) && tmp - '-');
if (tmp == '-') sgn = 0, tmp = getchar();
do res = (res << 3) + (res << 1) + tmp - '0';
while (isdigit(tmp = getchar()));
return sgn ? res : -res;
}
inline bool cmp(const int &a, const int &b)
{return sum[a] < sum[b];}
void Build(int L, int R, int D)
{
int Mid = ((L + R) >> 1) + 1, p = 0;
for (int i = L; i < R + 1; ++i)
if (tr[D][i] < Mid)
tr[D + 1][L + p] = tr[D][i],
sumL[D][i] = ++p;
else tr[D + 1][Mid + i - L - p] = tr[D][i],
sumL[D][i] = p;
if (p > 1) Build(L, Mid - 1, D + 1);
if (Mid < R) Build(Mid, R, D + 1);
//特别要注意这里,第一次打划分树的时候就错了,应该是Mid < R。
return;
}
int main()
{
freopen("piano.in", "r", stdin);
freopen("piano.out", "w", stdout);
n = getint(); K = getint();
L = getint(); R = getint();
for (int i = 1; i < n + 1; ++i)
sum[ord[i] = i] = getint() + sum[i - 1];
std::sort(ord + 1, ord + n + 1, cmp);
for (int i = 1; i < n + 1; ++i) tr[0][ord[i]] = i;
Build(1, n, 0);
for (int i = 1; i + L < n + 2; ++i)
hp.push(Node(i, i + L - 1, std::min(i + R - 1, n)));
while (K-- && !hp.empty())
{
Node ths = hp.top(); hp.pop(); ans += ths.val;
if (ths.rem > 1)
hp.push(Node(ths.L, ths.R1, ths.R2, ths.rem - 1));
}
std::cout << ans << std::endl;
return 0;
}