题目描述
给你一个序列 a a a,我们定义
S ( l , r ) = ∑ i = l i = r a i S_{(l,r)}=∑_{i=l}^{i=r}{a_i} S(l,r)=∑i=li=rai
显然易见我们会有 n ∗ ( n + 1 ) / 2 n∗(n+1)/2 n∗(n+1)/2个不同的 S ( l , r ) S_{(l,r)} S(l,r),请你输出其中前 k k k大的 S ( l , r ) S_{(l,r)} S(l,r)
输入描述
第一行输入两个整数 n , k n,k n,k 第二行输入 n n n个整数代表序列 a a a
输出描述
输出一行包含 k k k个整数代表答案
样例输入1
6 8
1 1 4 5 1 4
样例输出1
16 15 14 12 11 11 10 10
样例输入2
7 8
1 9 1 9 8 1 0
样例输出2
29 29 28 28 28 27 20 19
数据范围
0 ≤ a i ≤ 1 0 9 , 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ m i n ( 1 0 5 , n ∗ ( n + 1 ) / 2 ) 0≤a_i≤10^9,1≤n≤10^5,1≤k≤min(10^5,n∗(n+1)/2) 0≤ai≤109,1≤n≤105,1≤k≤min(105,n∗(n+1)/2)
解题思路
根据题目,好像应该采用贪心算法,所以我们先来尝试一下。
对于序列1 2 100 3 100 4 5 6
,我们尝试找出最大的
k
k
k个数:
第一大的是 s u m = 221 sum=221 sum=221毫无疑问;
第二大的是 s u m − 1 = 220 sum-1=220 sum−1=220;
第三大的是 s u m − 1 − 2 = 218 sum-1-2=218 sum−1−2=218;
第四大的是 s u m − 6 = 215 sum-6=215 sum−6=215,它并没有在第三大的基础上继续更新;
第五大的是 s u m − 1 − 2 − 6 = 212 sum-1-2-6=212 sum−1−2−6=212,它并没有再次减去新的元素;
模拟到这里我们应该发现了,并没有贪心算法可以采用,我们只能搜索每一种可能的情况,然后找出top_k
。
这里采用bfs
的算法:
void bfs() {
//初始化
while () {//终止条件
//bfs主体
}
}
然后我们来实现具体的bfs
代码。
首先明确bfs
的功能是搜索并输出top_k
:
void bfs(int k) {
//初始化
while (k--) {//终止条件
//bfs主体
}
}
尝试每一种可能的情况,实际上就是尝试每一个区间 [ l , r ] [l,r] [l,r]。
为了快速找出top_k
,我们采用优先队列维护已搜索到的结果。
我们将区间
[
l
,
r
]
[l,r]
[l,r]加和用作比较的键值key
。
priority_queue<pair<pair<int, int>, int>>pq;//默认大根堆
void bfs(int k) {
//初始化
pq.push({{ arr[n],1 },n })
int sum, l, r;
while (k--) {//终止条件
//bfs主体
sum = pq.top().first.first;
l = pq.top().first.second;
r = pq.top().second;
pq.pop();
cout << sum << ' ';
...
}
}
最后是最重要的部分,每一步需要做什么:
1)尝试区间 [ l + 1 , r ] [l+1,r] [l+1,r];
2)尝试区间 [ l , r − 1 ] [l,r-1] [l,r−1]。
priority_queue<pair<pair<int, int>, int>>pq;//默认大根堆
void bfs(int k) {
//初始化
pq.push({{ arr[n],1 },n })
int sum, l, r;
while (k--) {//终止条件
//bfs主体
sum = pq.top().first.first;
l = pq.top().first.second;
r = pq.top().second;
pq.pop();
cout << sum << ' ';
pq.push({{ arr[r]-arr[l],l+1 },r });
pq.push({{ arr[r-1]-arr[l-1],l },r-1 });
}
}
但是这样会产生对同一区间的重复搜索,所以我们加上一个去重判断。
priority_queue<pair<pair<int, int>, int>>pq;//默认大根堆
void bfs(int k) {
//初始化
pq.push({{ arr[n],1 },n })
int sum, l, r;
while (k--) {//终止条件
//bfs主体
sum = pq.top().first.first;
l = pq.top().first.second;
r = pq.top().second;
pq.pop();
cout << sum << ' ';
pq.push({{ arr[r]-arr[l],l+1 },r });
if (l == 1) pq.push({{ arr[r-1]-arr[l-1],l },r-1 });
}
}
最后,AC代码如下:
#include <iostream>
#include <queue>
using namespace std;
const int max_n = 1e5;
priority_queue<pair<pair<long long, int>, int>>pq;//默认大根堆
long long arr[max_n + 1], n;
void bfs(int k) {
//初始化
pq.push({ { arr[n],1 },n });
long long sum, l, r;
while (k--) {//终止条件
//bfs主体
sum = pq.top().first.first;
l = pq.top().first.second;
r = pq.top().second;
pq.pop();
cout << sum << ' ';
pq.push({ { arr[r] - arr[l],l + 1 },r });
if (l == 1) pq.push({ { arr[r - 1] - arr[l - 1],l },r - 1 });
}
}
int main() {
int k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
arr[i] += arr[i - 1];
}
bfs(k);
return 0;
}
可能会有人对为什么能直接输出pq.top().first.first
以及if (l == 1)
有疑问,所以在最后的最后做一下解释:
在每一轮bfs
,我们首先取出队首,然后在此基础上尝试两种不同的缩短区间方法。
区间缩短,所以新的结果一定小于队首,可以保证直接输出的结果是我们想要的结果。
但是新的结果仍然可能大于其他旧的结果,所以我们简单的采用if (l == 1)
的去重方式是否过于草率?
试着假设我们没有尝试的区间 [ l , r − 1 ] [l,r-1] [l,r−1]之和大于其他旧的结果。
我们已知两个条件:1)这个区间会被重复搜索;2)新的结果一定小于队首。
很容易推出已知条件与假设矛盾,所以没有被尝试的区间不会对结果正确性产生影响。