常用技巧:双向搜索/折半枚举
有的枚举问题超出了复杂度限制,直接进行枚举不可取,但是能够枚举一半的集合。将原问题分为两半分别枚举,再选择合适的方法进行来对两边枚举结果匹配,复杂度最多可以下降为原来的开平方。
例题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); }