题解 P4799 【[CEOI2015 Day2]世界冰球锦标赛】
题解
透彻
首先看数据范围
-
1-4组数据
N\leq20 ,爆搜就可以解决。inline void dfs(R ll dep,R ll sum){ if(sum>m)return; if(dep==n+1){ ans++; return; } dfs(dep+1,sum+a[dep]); dfs(dep+1,sum); } int main(){ read(n);read(m); for(R int i=1;i<=n;i++)read(a[i]); if(n<=20){ dfs(1,0); printf("%lld\n",ans); } return 0; } -
5-7组数据
M\leq10^6 ,裸的背包啊。int main(){ read(n);read(m); for(R int i=1;i<=n;i++)read(a[i]); if(m<=1e6){ f[0]=1; for(R int i=1;i<=n;i++) for(R int j=m;j>=a[i];j--) f[j]+=f[j-a[i]]; for(R int i=0;i<=m;i++)ans+=f[i]; printf("%lld\n",ans); } return 0; } -
现在你已经能拿到70分了(但在洛谷上是47分)
下面引出主角——折半搜索(meet in the middle思想)
因为
搜索
画一个图(网上找的不错的图)理解一下为什么能降低复杂度
inline void dfs(R int l,R int r,R ll sum,R ll a[],R 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);//不选
}
将前一半的搜索状态存入a数组,后一半存入b数组。
mid=n/2;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);
一般
我们可以现将a或b数组sort,让其有序。
然后通过枚举另一个数组中的状态,来实现统计答案。
上述找
sort(suma+1,suma+1+cnta);//使一个数组有序
for(R int i=1;i<=cntb;i++)
ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;//统计ans
下面是高清完整code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
#define N 55
using namespace std;
template<typename T>inline void read(T &a){
char c=getchar();T x=0,f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
a=f*x;
}
ll n,m,w[N],mid,suma[1<<21],sumb[1<<21],cnta,cntb,ans;
inline void dfs(R int l,R int r,R ll sum,R ll a[],R 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(){
read(n);read(m);
for(R int i=1;i<=n;i++)read(w[i]);
mid=n>>1;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);
sort(suma+1,suma+1+cnta);
for(R int i=1;i<=cntb;i++)
ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;
printf("%lld\n",ans);
return 0;
}
这里还有一道折半搜索的好题,难度升级——luogu,还有 my blog.