昼夜切换动画

题解 luogu P7841 「PMOI-4」生成树

题意

\(n\) 个数,第 \(i\)​ 个数的原始权值是 \(w_i\)​​​,按某种顺序选择这些数。

若当前是第 \(i\)​ 次选数,选择的当前数的权值为 \(k\)​ ,则其他所有未被选过的数的权值均加上 \((-1)^{i+k+1} \times k\)​。

求最大的权值和。

我们知道,\((-1)^x\) 的结果只有两种,\(1\)\(-1\) ,既然我们要求最大权值和,那我们就尽量加上一个正数,我们发现,\((-1)^{i+k+1} \times k\) 的正负与 \(k\)\(i+k+1\) 有关,于是我们可以对这两个进行分类讨论。

\(k\geq 0\) 的时候,我们希望 \((-1)^x\)​ 是正的,也就是希望 \(x\) 是偶数。

\(k<0\) 的时候,我们希望 \((-1)^x\) 是负的,也就是希望 \(x\) 是奇数。

于是我们可以讨论 \(i+k+1\) 的奇偶,由于 \(k+1\) 是定值,我们不妨预处理出来,讨论 \(i\) 的奇偶。

拿样例来说:

k 1 -1 -2 2 -3 3 4
k+1 2 0 -1 3 -2 4 5

对于第一个数 \(1\)​ ,\(k+1=2\)​ ,因为 \(k>0\)​,\(2\)​ 是偶数,所以我们希望此时的 \(i\)​ 是偶数。

对于第三个数 \(-1\)​,\(k+1=-1\)​ 因为 \(k<0\)​ ,\(-1\)​ 是奇数,所以我们希望 \(i\)​ 是偶数。

对于第四个数 \(2\)​,\(k+1=3\)​ 因为 \(k>0\)​ ,\(3\)​ 是奇数,所以我们希望 \(i\)​ 是奇数。

对于第五个数 \(-3\)​,\(k+1=-2\)​ 因为 \(k<0\)​ ,\(-2\)​ 是偶数,所以我们希望 \(i\)​ 是奇数。

于是我们总结出一个规律,

\(\geq 0\) \(<0\)
奇数 奇数 偶数
偶数 偶数 奇数

行代表 \(k\) 的正负 ,列代表 \(k+1\) 奇偶,填的数代表我们希望当前的 \(i\) 的奇偶。

然后我们将给出的数分成两类,一类是希望 \(i\)​ 是奇数的,放入 \(js\) 数组,一类是希望 \(i\) 是偶数的,放入 \(os\)​ 数组。

	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&mp[i])
		int x=mp[i]+1;//k+1
		if(abs(x)%2==0) //偶数 
		{
			if(mp[i]>=0)//大于 0  
				os[++o]=mp[i];//希望他加上一个偶数 
			else 
				js[++j]=mp[i];
		}
		else //奇数 
		{
			if(mp[i]<=0)
				os[++o]=mp[i];//希望他加上一个偶数变成奇数使得答案为正 
			else
				js[++j]=mp[i];
		}
	}

我们考虑选择一个数对答案的贡献,给剩下的数加 \((-1)^{i+k+1} \times k\)​,就相当于我们在最后加一个 \(k\times res\)​ ,\(res\)​​ 表示除了当前数序列里还未选择的数的个数。\(res\)​ 是确定的,我们可以贪心的选择让当前的 \(k\)​​ 是大的,当然,我们考虑的是加上一个正数的时候。后面如果要减去一个数,我们要尽量减去一个最小的值。数的原始值也是要加的,直接预处理就可以。

因此,我们可以将两个数组排下序,对答案没有影响。

下面我们考虑如何选数,如果两个数组里面的数是相等时,我们直接一个里面选一个选完就可以了,但是如果两个数组里面的个数不相等,我们就要继续讨论。

	sort(os+1,os+1+o,cmp);
	sort(js+1,js+1+j,cmp);
	while(min(o,j))
	{
		Ans+=abs(js[j])*cnt,j--,cnt--;
		Ans+=abs(os[o])*cnt,o--,cnt--;
	}

我们知道两个数组在上述操作后一定有一个为空,考虑选择剩下的数组里的数。

如果 \(os\)​​ 数组有剩余:

我们的数组的定义是希望在偶数次被选择,也就是在偶数次选择是对答案贡献更大,所以我们尽量实现让它在偶数次被选择,如果不行,就让它在奇数次选择是损失尽可能的小。

我们当前的操作肯定既有奇数也有偶数,当前则选择为奇数是,我们选择序列中最小的数,做到最小损失,当前选择为偶数时,我们选择序列中最大的数,做到最大贡献。

\(js\) 数组也是这样的思路。

至于实现,我是用了两个指针一个在前一个在后扫,因为序列是已经排过序的,可以直接得到最大最小值。

最后,因为我们是尽量加一个正数,但我们两个数组里存的是原始值,因此做加减的时候要取绝对值。其实数组里存绝对值也可以,只是加减的时候要注意。

下面附上完整代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>

#define int long long
#define INF 0x3f3f3f3f
#define MAX(a,b) (a)>(b)?(a):(b)
#define MIN(a,b) (a)>(b)?(a):(b)

using namespace std;
const int mod=998244353;
const int M=1e5+5;
int n;
int read()
{
	int x=0,y=1;
	char c=getchar();
	while(c<'0' || c>'9') {if(c=='-') y=0;c=getchar();}
	while(c>='0' && c<='9') { x=x*10+(c^48);c=getchar();}
	return y?x:-x;
}
int mp[M],js[M],os[M],j,o,Ans,cnt;
bool cmp(int a,int b)
{
	return abs(a)<abs(b);
}

signed main()
{
//	freopen("date.in","r",stdin);
	n=read();cnt=n-1;
	for(int i=1;i<=n;i++)
	{
		mp[i]=read();
		int x=mp[i]+1;
		if(abs(x)%2==0) //偶数 
		{
			if(mp[i]>=0)//大于 0  
				os[++o]=mp[i];//希望他加上一个偶数 
			else 
				js[++j]=mp[i];
		}
		else //奇数 
		{
			if(mp[i]<=0)
				os[++o]=mp[i];//希望他加上一个偶数变成奇数使得答案为正 
			else
				js[++j]=mp[i];
		}
		Ans+=mp[i];//预处理答案,提前先加上
	}
	sort(os+1,os+1+o,cmp);
	sort(js+1,js+1+j,cmp);
	while(min(o,j))//能够依次减的先都减去
	{
		Ans+=abs(js[j])*cnt,j--,cnt--;
		Ans+=abs(os[o])*cnt,o--,cnt--;
	}
	if(o==0&&j)//对于剩下的数 
	{
		int l=1,r=j;
		while(l<=r)
		{
			if((n-cnt)%2)//当前选的的是第奇数个数 
			Ans+=abs(js[r])*cnt,cnt--,r--;
			else
			Ans-=abs(js[l])*cnt,cnt--,l++;
		}
	}
	if(j==0&&o)//偶数有剩下 
	{
		int l=1,r=o;
		while(l<=r)
		{
			if((n-cnt)%2==0)//当前选的的是第偶数个数 
				Ans+=abs(os[r])*cnt,cnt--,r--;
			else
				Ans-=abs(os[l])*cnt,cnt--,l++;
		}
	}
	printf("%lld\n",Ans);			
	return 0;
}

/*
5
1 2 3 4 5

8
-2 5 -3 6 7 8 11 13
*/

昨天比赛的时候死活调不出来这道题,今天拿同学的代码拍了拍发现就了个小地方。大家一定要注意复制一段相似的代码的时候变量名一定要改!!!

posted @ 2021-08-21 14:16  smyslenny  阅读(239)  评论(0编辑  收藏  举报