洛谷P4799—— [CEOI2015 Day2]世界冰球錦標賽(折半搜尋)

豆沙呀發表於2020-11-17

原題連結
題意:
給n個物品和對應的價格,問用不超m元的錢可以買到的物品的方案數。
思路:
折半搜尋的入門題(霧.jpg)
n的範圍是n<=40。普通的搜尋一定會TLE,考慮將物品分成兩部分來搜尋,這樣時間複雜度就變成了2^(?/2+1);
基本思路就是對[1,mid] 和[mid+1],n兩個區間都進行dfs,分別記錄答案,再將兩個答案區間合併。
就本題而言,a陣列表示對[1,mid]進行搜尋,得到的方案;b陣列表示對[mid+1,n]進行搜尋,得到的方案。
區間合併時,對於a,b陣列裡的任意兩個數,只要和<=m都是合法的方案數,樸素解法是兩層for列舉,只能過40%的資料。優化是先將a陣列從小到大排序,再遍歷b陣列,在a陣列裡二分查詢b[i]+a[pos]<=m的最大的pos值,這樣pos之前的數一定也滿足該式。
程式碼:
注意陣列大小。


const int maxn=1e7+100;
ll n,m;
ll w[maxn],a[maxn],b[maxn];
ll cnta=0,cntb=0;
void dfs(ll l,ll r,ll sum,ll a[],ll &cnt){
    if(sum>m) return ;///剪枝
    if(l>r){
        a[++cnt]=sum;return ;///搜尋完成
    }
    dfs(l+1,r,sum+w[l],a,cnt);///選當前數
    dfs(l+1,r,sum,a,cnt);///不選當前數
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) w[i]=read();
    ll mid=n/2;///折半搜尋
    dfs(1,mid,0,a,cnta);
    dfs(mid+1,n,0,b,cntb);
    sort(a+1,a+1+cnta);///對a陣列進行排序
    ll res=0;
    for(int i=1;i<=cntb;i++){
        ///遍歷b陣列 看b[i]和前面的幾個數的和不超過m
        res+=upper_bound(a+1,a+1+cnta,m-b[i])-(a+1);
    }
    out(res);
    return 0;
}


參考

相關文章