1. 程式人生 > 實用技巧 >AtCoder Grand Contest 029 B-Power of two 題解

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; }
View Code

(可以證明,現在立刻取完b或a不會導致壞結果(即得不償失,湊得對變少),取完a顯然是因為a已經沒有“利用價值”了;取完b的話,讀者可能擔心以後b與其他數配對的情況,下給出證明(若有a個1,b個3,和c個7,這種情況配對的最多”二進位制數”為min(a,b+c),當a<c時,並不影響最終結果就是min(a,b+c)=a))。