zoukankan      html  css  js  c++  java
  • 题解 luogu P7841 「PMOI4」生成树

    题意

    \(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
    */
    

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

  • 相关阅读:
    【QT】对话框打开图像并用QPixmap显示
    【QT】打开文件对话框,选择路径下文件
    狄拉克下采样
    Linux 安装JDK(jdk-8u121-linux-x64.tar.gz)
    Linux 命令安装bin文件
    Python3 tesseract加载chi_sim异常停止工作
    Python3 pip出现Fatal error in launcher: Unable to create process using '"'
    Python3 判断文件和文件夹是否存在、创建文件夹
    Python3 itchat实现微信定时发送群消息
    Python3 实现(wxpy)用微信自动定时给朋友定时推广
  • 原文地址:https://www.cnblogs.com/jcgf/p/15169434.html
Copyright © 2011-2022 走看看