题意简述
有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;
}