[题解] AT4501 [AGC029B] Powers of two

该博客主要介绍了一种算法问题,即如何找到一个整数数组中,可以配对消去和为2的幂次方的数对的最大数量。博主通过分析指出,每次消去最大和的数对能保证最优解,并提出使用二进制 Trie 数据结构来高效地找到配对的数。在代码实现中,博主强调了处理不同位数的数时要避免重复,并给出了完整的 C++ 代码示例。
摘要由CSDN通过智能技术生成

原题传送门

题意简述

有n个数,每次可以选择一对和为2的幂次方的数并消去,问最多有几组可以消去。

题解

对于一组数而言,如果其中两个数可以消去,则消去的数之和最大时答案最优。
简证如下:首先对于一个数而言,与其配对的比它小的数至多有一个,假设 a i + a j = 2 n ( a i < a j ) a_i+a_j=2^n(a_i<a_j) ai+aj=2n(ai<aj),如果不消去这一组,那么 a j a_j aj就没有消去的机会了(即使等于 a i a_i ai的数有很多个话按照这样做也是不会消去的),那么 a j a_j aj就不可能消去了。然而就算 a i a_i ai可以和其他的数组合,最终的答案也不会更优。

然后考虑怎么去寻找配对的数。
我们可以发现,将x转为二进制后并倒序,除了最左边的一位1的左边均相同,在它右边的每一位都应该是不同的(否则会导致无法一直进位)。
举个例子,假设 a i = 4 , a j = 12 a_i=4,a_j=12 ai=4,aj=12
将其转成二进制可得:
a i = 100 , a j = 1100 a_i=100,a_j=1100 ai=100,aj=1100
将其倒序可得
a i = 001 , a j = 0011 a_i=001,a_j=0011 ai=001,aj=0011

可以考虑使用trie,将每位以二进制的形式存下来,然后就可以方便地找到应该找的数。
注意:当两个数位数不同时,在位数较小的那个数操作完后,应一直向’1’位跳,这样就可以保证后面的数位也是一直进位的,并且保证当前消去的是最大和。
建议边消数边新加数,否则会导致重复的数出现,较难处理。

于是代码可以容易地写出。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define Maxn 200010
using namespace std;
int n,a[Maxn],q[32*Maxn][2],s[32*Maxn],cnt,ans;
int read()
{
	int s=0,w=1;char ch=getchar();
	while (!isdigit(ch))
	{
		if (ch=='-') w=-1;
		ch=getchar();
	}
	while (isdigit(ch)) s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
	return s*w;
}
bool cmp(int x,int y){return x>y;}
int main()
{
	n=read();
	for (int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+n+1,cmp);
	for (int i=1;i<=n;i++)
	{
		int x=a[i],now=0,flag=0,fir=0,lst=0;
		//lst表示是上一个'1'的位置
		while (x)
		{
			int k=x&1;
			if (fir) k^=1;
			if (k) fir=1;
			if (!q[now][k]) {flag=1;break;}
			now=q[now][k];
			if (k) lst=now;
			x>>=1;
		}
		if (!flag)
			while (1)
			{
				if (!q[now][1])
				{
					if (s[lst]) ++ans,s[lst]--,flag=0;
					else flag=1;						//注意这里,博主因为忘记加挂了无数次。
					break;
				}
				now=q[now][1];
				if (s[now]) lst=now;
			}
		if (flag)
		{
			x=a[i],now=0;
			while (x)
			{
				int k=x&1;
				if (!q[now][k]) q[now][k]=++cnt;
				now=q[now][k];
				x>>=1;
			}
			s[now]++;
		}
	}
	printf("%d\n",ans);
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值