Luogu P7841 生成树

题意

给定 n n n 个数的原始权值 w i w_i wi,你要按照某种顺序将它们排序。

若第 i i i 个数的原始权值 k k k,则第 i + 1 i+1 i+1 到第 n n n 个数的权值均加上 ( − 1 ) i + k + 1 × k (-1)^{i+k+1} \times k (1)i+k+1×k

你需要求出一种排序方案,使得 n n n 个数的权值和最大。

solution

理论分析

对于位置 i i i 上的数,它的总贡献为 ( n − i ) ( − 1 ) i + k + 1 × k (n-i)(-1)^{i+k+1} \times k (ni)(1)i+k+1×k

一个很明显的贪心策略就是贡献大的数放在前面。

那么对于位置 i i i 我们如何确定应该放哪个数呢?最暴力的方法就是枚举每一个数,计算它的贡献,找出最大的。

max ⁡ { ( − 1 ) i + w i + 1 × w i } \max\{(-1)^{i+w_i+1} \times w_i\} max{(1)i+wi+1×wi}

很显然,这样的做法是一定会超时的。我们要做的就是缩小候选数的范围,也就是找出哪些数是有可能为最优答案的,一定不可能的我们就不枚举它。

观察计算贡献的式子,不难发现候选数的奇偶和正负均会影响贡献的正负。

也就是说一共有四种组合:

正奇数

正偶数

负奇数

负偶数

对于 i i i 是奇数的情况,我们要从能得到正贡献的两个组合(即正偶数和负奇数)中选绝对值更大的。

如果没有可以得到正贡献的数怎么办,此时只存在负偶数和正奇数,我们要选择绝对值最小的。

可以发现不管是否有正贡献,当 i i i 为奇数时我们选择的都是最大的偶数和最小的奇数。

同理,我们可以推出当 i i i 为偶数时我们选择的都是最大的奇数和最小的偶数。

所以对于每一个位置,我们只需要比较最大的奇数,最大的偶数,最小的奇数,最小的偶数就能得到最大贡献。

实现方法

我们在输入时将奇数和偶数分别放入两个队列中,并从小到大排序,这样每次只需取出两个队列的队头和队尾这四个数进行比较,就能确定答案。

int js[100010];//奇数队列
int os[100010];//偶数队列
int p=1,up;//奇数队头和队尾
int q=1,down;//偶数队头和队尾
for(int i=1,w;i<=n;i++){
	cin>>w;
	if(w%2) up++,js[up]=w;
	else down++,os[down]=w;
}
//排序
sort(js+1,js+up+1);
sort(os+1,os+down+1);

接下来确定每一个位置上的数,要注意的是我们需要判断队列是否为空。

for(int i=1;i<=n;i++){
	int addp=((i+js[p]+1)%2==0)?js[p]:-js[p];//最小奇数的贡献
	int addq=((i+os[q]+1)%2==0)?os[q]:-os[q];//最小偶数的贡献
	int addup=((i+js[up]+1)%2==0)?js[up]:-js[up];//最大奇数的贡献
	int adddown=((i+os[down]+1)%2==0)?os[down]:-os[down];//最大偶数的贡献
	if(p>up) addp=addup=-inf;//奇数已经取完
	if(q>down) addq=adddown=-inf;//偶数已经取完
	if(addp==max(max(max(addp,addq),addup),adddown)){
		f[i]=js[p];
		p++;
	}else if(addq==max(max(max(addp,addq),addup),adddown)){
		f[i]=os[q];
		q++;
	}else if(addup==max(max(max(addp,addq),addup),adddown)){
		f[i]=js[up];
		up--;
	}else {
		f[i]=os[down];
		down--;
	}
}

完成这一步后我们就得到了一个重新排好顺序的数组 f f f,按题目要求求出答案即可

ll calc(){
	ll add=0;
	ll sum=0;
	for(int i=1;i<=n;i++){
		sum+=f[i]+add;
		add+=((i+f[i]+1)%2==0)?f[i]:-f[i];
	}
	return sum;
}

完整代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,ans;
int up,down,p=1,q=1,inf=1e9+7;
int f[100010];
int js[100010];
int os[100010];
ll calc(){
	ll add=0;
	ll sum=0;
	for(int i=1;i<=n;i++){
		sum+=f[i]+add;
		add+=((i+f[i]+1)%2==0)?f[i]:-f[i];
	}
	return sum;
}
int main(){
	cin>>n;
	for(int i=1,w;i<=n;i++){
		cin>>w;
		if(w%2) up++,js[up]=w;
		else down++,os[down]=w;
	}
	sort(js+1,js+up+1);
	sort(os+1,os+down+1);
	for(int i=1;i<=n;i++){
		int addp=((i+js[p]+1)%2==0)?js[p]:-js[p];
		int addq=((i+os[q]+1)%2==0)?os[q]:-os[q];
		int addup=((i+js[up]+1)%2==0)?js[up]:-js[up];
		int adddown=((i+os[down]+1)%2==0)?os[down]:-os[down];
		if(p>up) addp=addup=-inf;
		if(q>down) addq=adddown=-inf;
		if(addp==max(max(max(addp,addq),addup),adddown)){
			f[i]=js[p];
			p++;
		}else if(addq==max(max(max(addp,addq),addup),adddown)){
			f[i]=os[q];
			q++;
		}else if(addup==max(max(max(addp,addq),addup),adddown)){
			f[i]=js[up];
			up--;
		}else {
			f[i]=os[down];
			down--;
		}
	}
	cout<<calc();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ycw-123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值