AtCoder Grand Contest 029 B-Power of two 题解
题目链接 https://atcoder.jp/contests/agc029/tasks/agc029_b
这题题意为n个正数要求两两搭配形成"二进制数"(2^n这样的数,例如2 4 8 16...),数字可以不用完(一般想用也用不完),且每个数字只能用一次.
例: 3 11 14 5 13最多组成16(3+13) 16(5+11)这两个“二进制数”。
面对这题,很多人会陷入选择困难症,因为可选择的组合的结果——目标数——“二进制数”很多样,上一个例子就有3+5=8这种因选择错误而导致得不偿失的坏结果。
一旦算法要考虑的过多,程序就不好写,太多要考虑了。
所以找对方法,这题AC不是梦。
我听说过两种方法:一种是把这题转化成图论做的:每个数字作为一个点,和其他点若能形成“二进制数”就产生一条连线,最后就想办法统筹最多的路线(思路应该是这样子,图论这方面我完全不会,就讲这么多,当这个方法”抛砖引玉”吧);
另一种是想办法避免做选择,实现方法是从大的数字开始为这个数字找伙伴,假设当前最大数是a,而a的组成目标仅仅能是大于a的最小二进制数(设为target),因为我们是从最大的数字到最小的数字开始找伙伴的,所以a和当前第二大的数字b的和 <= 2*a < 2*target, 也就是说,a的组成目标不可能是比target更大的“二进制数”(2*target),也不可能是比target更小的"二进制数"(因为a>=target/2)。 那好了,我们知道手上的一个材料且知道唯一的目标,那么查找另一个材料(target-a)(下称作b)存在否就解决了眼前的问题(这就是贪心),以此类推。
实现方法是用map存储输入数据,键是数字本身,附加元素是该数字的个数,遇到如上查找能匹配的对立即把所有能转换的都转换掉
#include<iostream> #include<algorithm> #include<cmath> #include<map> #include<functional> using namespace std; #define maxn long long(5*1e8) int main(){ long long n=0; scanf("%d",&n); map<long long,long long,greater<long long>> num; for(long long i=1;i<=n;++i){ long long temp=0; scanf("%d",&temp); num[temp]++; } //目标“二进制数” long long j=pow(2.0,31),ans=0; for(auto iter=num.begin();iter!=num.end();++iter){ while(iter->first<j/2) j/=2;//减小目标,找到合适的目标(大于iter->first的最小"二进制数") if(num[iter->first]>0&&num[j-iter->first]>0){ //特殊情况,iter->first自己就是二进制数 if(j==iter->first*2){ ans+=num[iter->first]/2; num[iter->first]=num[iter->first]%2; } else{ //贪心取完 ans+=min(num[iter->first],num[j-iter->first]); num[j-iter->first]-=min(num[iter->first],num[j-iter->first]); } } } cout<<ans<<endl; }
(可以证明,现在立刻取完b或a不会导致坏结果(即得不偿失,凑得对变少),取完a显然是因为a已经没有“利用价值”了;取完b的话,读者可能担心以后b与其他数配对的情况,下给出证明(若有a个1,b个3,和c个7,这种情况配对的最多”二进制数”为min(a,b+c),当a<c时,并不影响最终结果就是min(a,b+c)=a))。

浙公网安备 33010602011771号