世界冰球锦标赛【折半搜索】

>Link

luogu P4799


>Description

给出预算 M M M N N N场比赛的票价,试求:如果总票价不超过预算,有多少种观赛方案。
如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。

1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 , a i ≤ 1 0 16 1\le N \le 40,1\le M \le 10^{18},a_i \le 10 ^{16} 1N40,1M1018,ai1016


>解题思路

部分分 N ≤ 20 N\le 20 N20:很容易想到搜索,当前的比赛有选或不选两种选择

但是 N ≤ 40 N\le 40 N40 2 40 2^{40} 240很明显会爆
所以这里引进 折半搜索 (meet in the middle,顾名思义就是在中间相遇
考虑一种问题,问从 1 1 1 N N N 的路径有多少条,爆搜会T 并且会进行一些无意义的搜索,就是搜到一条路,这条路往下走一定不可能到达 N N N
我们把搜索的层从中间分隔开,先从 1 1 1 搜到中间某点 A A A,从 N N N 搜到中间某点 B B B,如果 A A A 可以走到 B B B,那就有一条路径 1 > > A > > B > > N 1>>A>>B>>N 1>>A>>B>>N。前提是路径都是无向的
这样时间复杂度从 O ( 2 n ) O(2^n) O(2n) 变成了 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2)

N N N 场比赛分成两部分,按照上面的思路,如果第一条“路径”的花费加上第二条“路径”的花费不超过预算,说明可行
我是直接把第一次搜索得到的结果存进数组,排序,第二次搜索时二分找对应的符合条件的第一次搜索的方案有多少个


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define LL long long
#define N 50
using namespace std;

int n, tot, T1, T2;
LL ans, m, a[N], q[2000000];

void dfs1 (int now, LL sum)
{
    
    
	if (sum > m) return;
	if (now > T1)
	{
    
    
		q[++tot] = sum;
		return;
	}
	dfs1 (now + 1, sum + a[now]);
	dfs1 (now + 1, sum);
}
void dfs2 (int now, LL sum)
{
    
    
	if (sum > m) return;
	if (now < T2)
	{
    
    
		ans += upper_bound (q + 1, q + 1 + tot, m - sum) - q - 1;
		return;
	}
	dfs2 (now - 1, sum + a[now]);
	dfs2 (now - 1, sum);
}

int main()
{
    
    
	scanf ("%d%lld", &n, &m);
	for (int i = 1; i <= n; i++) scanf ("%lld", &a[i]);
	T1 = n / 2 + (n & 1);
	T2 = T1 + 1;
	dfs1 (1, 0);
	sort (q + 1, q + 1 + tot);
	dfs2 (n, 0);
	printf ("%lld", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43010386/article/details/121150487