[Daimayuan] 贪就是了(C++,BFS)

题目描述

给你一个序列 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) 0ai109,1n105,1kmin(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 sum1=220

第三大的是 s u m − 1 − 2 = 218 sum-1-2=218 sum12=218

第四大的是 s u m − 6 = 215 sum-6=215 sum6=215,它并没有在第三大的基础上继续更新;

第五大的是 s u m − 1 − 2 − 6 = 212 sum-1-2-6=212 sum126=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,r1]

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 
#include 
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,r1]之和大于其他旧的结果。

我们已知两个条件:1)这个区间会被重复搜索;2)新的结果一定小于队首。

很容易推出已知条件与假设矛盾,所以没有被尝试的区间不会对结果正确性产生影响。

你可能感兴趣的:(DFS与BFS,c++,宽度优先,算法)