校内集训 位位运算 题解

校内集训 位位运算 题解

写在前面

由于教练要求现在每天都要写题解,所以之后每篇题解的质量可能有所下降。这些质量不那么高的题解会专门放在个人练习题解

题目

题目描述

昨夜做了一个梦,梦里我们回到手牵着手,醒来的失落……

你只记得你在梦里面,你想到了这样一个问题。给定 N N N 个非负整数,每次你可以选择两个数 a , b a,b a,b,将其中一个数变为 a and b 另一个变成 a or b,你可以进行多次操作,任何时候都可以停止,请最大化所有数的平方和。

输入

第一行包括一个正整数 N N N

第二行包括N个用空格分开的非负整数 A i A_i Ai

对于 40 % 40\% 40% 的数据: 1 ⩽ N ⩽ 5 , A i ⩽ 1000 1\leqslant N\leqslant 5,A_i\leqslant 1000 1N5,Ai1000

对于 70 % 70\% 70% 的数据: 1 ⩽ N ⩽ 1000 , A i ⩽ 1000 1\leqslant N\leqslant 1000,A_i\leqslant 1000 1N1000,Ai1000

对于 100 % 100\% 100% 的数据: 1 ⩽ N ⩽ 1 0 5 , A i ⩽ 1 0 6 1\leqslant N\leqslant 10^5,A_i\leqslant 10^6 1N105,Ai106

输出

一行一个非负整数表示最后最大化的所有数的平方和。

样例输入

5
1 2 3 4 5

样例输出

99

提示

一组最优方案是变成 7 , 0 , 7 , 0 , 1 7,0,7,0,1 7,0,7,0,1,答案是 99 99 99

分析

看到这简短的题目描述和无限次操作次数和仍何时候都可以停止的限制就想到这道题多半就是推结论,结果还真是。

题目中两种操作都是位运算,所以我们来专门枚举位运算的每种情况。

对于(1,1) 1&1=1	1|1=1
对于(1,0) 1&0=0	1|0=1
对于(0,1) 0&1=0	0|1=1
对于(0,0) 0&0=0	0|0=0

我们可以发现:每位数不管是那种情况进行位运算后 1 1 1 的个数始终不会改变。而题目中又说我们可以进行无数次位运算,所以二进制下每个数每位上的 1 1 1 可以在同位上任意组合。

以样例为例:

1=(0001)B
2=(0010)B
3=(0011)B
4=(0100)B
5=(0101)B
每位上的1个数
   0233

又因为第 i i i 位(以最右边一位为第 1 1 1 位)上的每个 1 1 1 对应的值固定为 2 i − 1 2^{i-1} 2i1,所以无论我们怎么操作,所有数的总和始终不变。所以我们只要知道了如何将这些 1 1 1 进行组合使得平方之和最大就行了。

我们进行以下的证明:

设这些 1 1 1 中的一部分组成了两个数 a , b a,b a,b

∵ a 2 + b 2 = ( a + b ) 2 − 2 a b \because a^2+b^2=(a+b)^2-2ab a2+b2=(a+b)22ab a + b a+b a+b 为定值。

∴ \therefore a b ab ab 最小时 a 2 + b 2 a^2+b^2 a2+b2 最小。

∴ ∣ a − b ∣ \therefore|a-b| ab 应尽可能地大。

所以我们要不停地用剩下的 1 1 1 组出尽量大的数,直到 1 1 1 用完为止。

思路

用一个数组 w w w 统计每位的 1 1 1 的个数,然后按照上面所说的从 w w w 的高位往地位枚举,如果有剩余的 1 1 1 就拿出来用。

有个小细节需要注意:过程中要开 long long n = 1 n=1 n=1 需要特判。

代码

#include<cstdio>
using namespace std;
inline int getnum() {
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
inline void putnum(long long a) {
	if(a==0) {
		putchar('0');
		return;
	}
	if(a<0)	{
		putchar('-');
		a*=-1;
	}
	long long t=1;
	while(t<=a)
		t*=10;
	do {
		t/=10;
		putchar('0'+a/t%10);
	} while(t!=1);
}
inline int Max(int a,int b) {
	return a>b?a:b;
}
inline int Min(int a,int b) {
	return a>b?b:a;
}
int w[25];
int main() {
	int n=getnum();
	long long ans=0;
	long long temp,max=0,mem;
	for(int i=1; i<=n; i++) {
		mem=temp=getnum();
		for(int j=1; temp; j++,temp>>=1)
			w[j]+=1&temp;
	}
	if(n==1) {
		putnum(mem);
		return 0;
	}
	for(int i=1; i<=23; i++)
		max=Max(w[i],max);
	for(int i=1; i<=max; i++) {
		temp=0;
		for(int j=23; j>=1; j--) {
			temp+=(w[j]>0)<<(j-1),w[j]-=(w[j]>0);
		}
		ans+=temp*temp;
	}
	putnum(ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值