P4799 [CEOI2015 Day2] 世界冰球锦标赛

Portal.

折半搜索(meet in the middle)。

首先考虑正常的搜索,时间复杂度 O ( 2 40 ) O(2^{40}) O(240)

所以考虑折半搜索,从两边对着搜,具体就是搜索 1 ∼ n 2 1\sim\dfrac{n}{2} 12n n 2 + 1 ∼ n \dfrac{n}{2}+1\sim n 2n+1n 两个区间内的最优解。这样优化之后,时间复杂度就变为 O ( 2 n 2 + 1 ) O(2^{\frac{n}{2}+1}) O(22n+1)

第一遍搜索前半部分时,我们的限制是不超过 M M M。可以记录此时对于一个合法方案,它的消耗钱数 sum 是多少。

到了第二遍搜索后半部分时,需要知道的是,我们可以统计的方案是前后加一起小于 M M M 的方案。于是乎我们先对第一次得到的方案进行 sort,枚举选后一半的所有情况,如果和前一半合并之后总钱数仍小于 M M M,可以把这些可能全部累计到 ans 中。由于前一半的方案已经被我们排成有序的了,所以我们可以用 upper_bound 函数快速查找出可以统计的情况。

具体代码实现:

#include 
using namespace std;
#define int long long

// 对于此题的两次 DFS 来说,要统计 1 个 sum(当前消耗钱数)和 pos(已经搜到哪里了)

const int maxn=1<<25;//左移 25 位相当于 2 的 25 次方
int q1[maxn],q2[maxn],co[45],ans,N,M,cnt1,cnt2,mid;

void dfs1(int sum,int pos)
{
    if(sum>M) return;
    if(pos>mid) {q1[++cnt1]=sum;return;}
    dfs1(sum+co[pos],pos+1);
    dfs1(sum,pos+1);//考虑不选当前位,继续向下搜
}

void dfs2(int sum,int pos)
{
    if(sum>M) return;
    if(pos>N) {q2[++cnt2]=sum;return;}
    dfs2(sum+co[pos],pos+1);
    dfs2(sum,pos+1);
}

signed main()
{
    cin>>N>>M;
    mid=(N+1)/2;
    for(int i=1;i<=N;i++) cin>>co[i];
    dfs1(0,1);
    sort(q1+1,q1+cnt1+1);
    dfs2(0,mid+1);
    for(int i=1;i<=cnt2;i++)
    	ans+=upper_bound(q1+1,q1+cnt1+1,M-q2[i])-q1-1;
    // cout<
    // cout<
    cout<<ans;
    return 0;
}

你可能感兴趣的:(题解,搜索)