2022年蓝桥杯C++c组 G题重新排序 知识点:贪心+差分+前缀和

243 阅读2分钟

1.重新排序 - 蓝桥云课 (lanqiao.cn)

视频讲解

重新排序_哔哩哔哩_bilibili

样例解析

我刚开始想着要想区间和最大排个降序即可,但是这从大到小排实际上不是最优策略。

我们来验证一下,就拿样例来验证。

1 2 3 4 5,原数组[1,3],[2,5]这段区间和是20。

排完序之后是5,4,3,2,1。[1,3],[2,5]这段区间的和是22。

从大到小排序后的数组的区间和比原数组的区间和大2。

我们再给出一种排列:1,4,5,2,3,区间和为24,比原数组区间和大4。

因此,从大到小排一定不是最优策略。

我们这里直接给出一个最优策略:

出现次数较多的*较大值,得出来的总值就较大。

在初始状态1,2,3,4,5中, 2,3在区间[1,3]中出现了一次,在区间[2,5]中出现了一次。

排序后变为1,4,5,2,3后,重复出现的变为了4,5。

image.png

于是问题就变为了怎么统计出每个数出现的次数。

n,m都是1e5,我们需要遍历m次[l,r]区间的查询,最后遍历完n,统计每个数出现的次数,复杂度是O(n2)O(n^2),光到这里就超时了。

因此我们这样要优化一下,可以用差分数组来统计每个数出现的次数。刚开始所有数都初始化为1,表示都出现了一次。然后对于m个查询,每个查询都对[l,r]区间内的值+1,这样就统计出来了每个数出现的次数。

注意,差分数组可以在O(1)时间内某个区间都加上c,刚开始每个数是0:0,0,0,0,然后[1,3]区间内每个数又出现了一次,应该是1,1,1,0,0。但是差分数组是单点修改,只需要给两段加上1即可,应该是:1,0,1,0,0。然后我们再用一个前缀和恢复一下,变为1,1,1,0,0。

code

a[i]最大1e6,有相加部分,记得开long long

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
const int N = 1e5 + 10;
int a[N], cnt[N];

bool cmp(int x,int y)
{
	return x > y;
}
signed main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	
    cin >> m;
	while(m--)
	{
		int l, r; cin >>l>>r;

		cnt[l] ++, cnt[r + 1] --;
	}

	//对差分数组还原,得到的就是每个数的出现次数
	for (int i = 1; i <= n; i++)
	{
	 cnt[i] =cnt[i]+cnt[i - 1];
	}

	//统计一下刚开始的区间和
	int sum1=0;
	for (int i = 1; i <= n; i++)
	{
		sum1 += a[i] * cnt[i];
	}

	//排个序,让出现次数多的 和 值较大的 排前面
	sort(a + 1, a + n + 1,cmp);
	sort(cnt+1,cnt+n+1,cmp);

	//排完序的区间和
	int sum2 = 0;
	for (int i = 1; i <= n; i++)
	{
		sum2 += a[i] * cnt[i];
	}
	
	cout << sum2 - sum1 << endl;
	return 0;
}