常用技巧:双向搜索/折半枚举

有的枚举问题超出了复杂度限制,直接进行枚举不可取,但是能够枚举一半的集合。将原问题分为两半分别枚举,再选择合适的方法进行来对两边枚举结果匹配,复杂度最多可以下降为原来的开平方。

例题POJ2785,将O(n4)降为O(n2*lgn)。其中lower_bound和upper_bound实现了以lgn的复杂度匹配的功能,相当巧妙

#include<stdio.h>
#include<algorithm>
using namespace std;
int n,a[4005],b[4005],c[4005],d[4005],cd[16000005];
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cd[i*n+j]=c[i]+d[j];
        }
    }
    sort(cd,cd+n*n);
    int ans=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            int ab=-(a[i]+b[j]);
            ans+=upper_bound(cd,cd+n*n,ab)-lower_bound(cd,cd+n*n,ab);
        }
    }
    printf("%d\n",ans);
    return 0;
}

超大背包问题,DP的做法复杂度为O(n*V),这里明显不行。换一种思路,n在这里比较小,用枚举的方法复杂度为(2n)。但n=40还是超出了范围,而n=20可以接受,于是折半枚举,同时对上半部分的枚举结果进行适当筛选

pair<ll,ll> ps[1<<(n/2)];
void sovle(){
    int maxstate=(1<<(n/2))-1;
    for(int i=0;i<=maxstate;i++){
        ll sw=0,sv=0;
        for(int j=0;j<n/2;j++){
            if(i>>j&1){
                sw+=w[j];sv+=v[j];
            }
        }
        ps[i]=make_pair(sw,sv);
    }
    sort(ps,ps+maxstate+1);
    
    int m=1;
    for(int i=1;i<=maxstate;i++){
        if(ps[m-1].second<ps[i].second){
            ps[m++]=ps[i];
        }
    }
    
    ll ans=0;
    for(int i=0;i<(1<<(n-n/2));i++){
        ll sw=0,sv=0;
        for(int j=0;j<n-n/2;j++){
            if((i>>j)&1){
                sw+=w[n/2+j];
                sv+=v[n/2+j];
            }
        }
        if(sw<=W){
            ll tv=(lower_bound(ps,ps+m,make_pair(W-sw,INF))-1)->second;
            res=max(res,sv+tv);
        }
    }
    printf("%lld\n",res);
}

 

posted @ 2020-10-09 15:19  太山多桢  阅读(162)  评论(0编辑  收藏  举报