双向搜索
应用
起点和终点是已知的,需要确定从起点到终点需要多少步
例一
[CEOI2015 Day2] 世界冰球锦标赛
题目描述
译自 CEOI2015 Day2 T1「Ice Hockey World Championship」
今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。
给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。
输入格式
第一行,两个正整数 \(N\) 和 \(M(1 \leq N \leq 40,1 \leq M \leq 10^{18})\),表示比赛的个数和 Bobek 那家徒四壁的财产。
第二行,\(N\) 个以空格分隔的正整数,均不超过 \(10^{16}\),代表每场比赛门票的价格。
输出格式
输出一行,表示方案的个数。由于 \(N\) 十分大,注意:答案 \(\le 2^{40}\)。
样例 #1
样例输入 #1
5 1000
100 1500 500 500 1000
样例输出 #1
8
提示
样例解释
八种方案分别是:
- 一场都不看,溜了溜了
- 价格 \(100\) 的比赛
- 第一场价格 \(500\) 的比赛
- 第二场价格 \(500\) 的比赛
- 价格 \(100\) 的比赛和第一场价格 \(500\) 的比赛
- 价格 \(100\) 的比赛和第二场价格 \(500\) 的比赛
- 两场价格 \(500\) 的比赛
- 价格 \(1000\) 的比赛
有十组数据,每通过一组数据你可以获得 10 分。各组数据的数据范围如下表所示:
| 数据组号 | \(1-2\) | \(3-4\) | \(5-7\) | \(8-10\) |
|---|---|---|---|---|
| \(N \leq\) | \(10\) | \(20\) | \(40\) | \(40\) |
| \(M \leq\) | \(10^6\) | \(10^{18}\) | \(10^6\) | \(10^{18}\) |
此题不需要去重
std:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll m,a[50],ans;
int p;
ll ans1[1<<21],idx1,ans2[1<<21],idx2;
void dfs1(int k,ll sum)
{
if(sum > m)return;
if(k > p)
{
ans1[++idx1] = sum;
return;
}
dfs1(k+1,sum+a[k]);
dfs1(k+1,sum);
}
void dfs2(int k,ll sum)
{
if(sum > m)return;
if(k > n)
{
ans2[++idx2] = sum;
return;
}
dfs2(k+1,sum+a[k]);
dfs2(k+1,sum);
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i = 1;i<= n;i++)scanf("%lld",a+i);
p = n/2;
dfs1(1,0);
dfs2(p+1,0);
sort(ans1+1,ans1+1+idx1);
sort(ans2+1,ans2+1+idx2);
int p2 = idx2;
for(int i = 1;i <= idx1;i++)
{
while(ans2[p2] + ans1[i] > m)p2--;
ans += p2;
}
printf("%lld",ans);
return 0;
}
例二
P3067 [USACO12OPEN] Balanced Cow Subsets G
[USACO12OPEN] Balanced Cow Subsets G
题面翻译
我们定义一个奶牛集合 \(S\) 是平衡的,当且仅当满足以下两个条件:
- \(S\) 非空。
- \(S\) 可以被划分成两个集合 \(A,B\),满足 \(A\) 里的奶牛产奶量之和等于 \(B\) 里的奶牛产奶量之和。划分的含义是,\(A\cup B=S\) 且 \(A\cap B=\varnothing\)。
现在给定大小为 \(n\) 的奶牛集合 \(S\),询问它有多少个子集是平衡的。请注意,奶牛之间是互不相同的,但是它们的产奶量可能出现相同。
输入格式
第一行一个整数 \(n\),表示奶牛的数目。
第 \(2\) 至 \(n+1\) 行,每行一个数 \(a_i\),表示每头奶牛的产奶量。
输出格式
输出一个数表示方案总数。
样例解释
共存在三种方案。集合 \(\{1,2,3\}\) 可以划分为 \(\{1,2\}\) 与 \(\{3\}\);集合 \(\{1,3,4\}\) 可以划分为 \(\{1,3\}\) 与 \(\{4\}\);集合 \(\{1,2,3,4\}\) 可以划分为 \(\{1,4\}\) 与 \(\{2,3\}\),共 \(3\) 种子集。
数据范围及约定
对于全部数据,保证 \(1\le n\le 20\),\(1\le a_i\le 10^8\)。
题目描述
Farmer John's owns N cows (2 <= N <= 20), where cow i produces M(i) units of milk each day (1 <= M(i) <= 100,000,000). FJ wants to streamline the process of milking his cows every day, so he installs a brand new milking machine in his barn. Unfortunately, the machine turns out to be far too sensitive: it only works properly if the cows on the left side of the barn have the exact same total milk output as the cows on the right side of the barn!
Let us call a subset of cows "balanced" if it can be partitioned into two groups having equal milk output. Since only a balanced subset of cows can make the milking machine work, FJ wonders how many subsets of his N cows are balanced. Please help him compute this quantity.
输入格式
* Line 1: The integer N.
* Lines 2..1+N: Line i+1 contains M(i).
输出格式
* Line 1: The number of balanced subsets of cows.
样例 #1
样例输入 #1
4
1
2
3
4
样例输出 #1
3
提示
There are 4 cows, with milk outputs 1, 2, 3, and 4.
There are three balanced subsets: the subset {1,2,3}, which can be partitioned into {1,2} and {3}, the subset {1,3,4}, which can be partitioned into {1,3} and {4}, and the subset {1,2,3,4} which can be partitioned into {1,4} and {2,3}.
这题每一个元素有三种状态,放入\(A\)集合或放入\(B\)集合或不放
用折半搜索时间上可以但是比较难想的是去重,因为搜的直接是\(S\)(平衡集合)的子集(\(A\)和\(B\)相等)但\(S\)可能可以分成多个\(A\)和\(B\)这样会导致重复计算
\(N\)很小所以可以将每一个搜的平衡集合中\(S\)具体元素记录下来(二进制压缩)
在最后统计答案时vis数组记录已计算过的\(S\)
std:
#include<bits/stdc++.h>
using namespace std;
int n,a[22],ans;
struct AAA
{
int sum;
int state;
}l[1<<22],r[1<<22];
int ll,rr;
bool vis[1<<22];
void ldfs(int k,int A,int state)
{
if(k > n/2)
{
l[++ll].sum = A;
l[ll].state = state;
return;
}
ldfs(k+1,A,state);
ldfs(k+1,A+a[k],state|(1<<k));
ldfs(k+1,A-a[k],state|(1<<k));
}
void rdfs(int k,int A,int state)
{
if(k > n)
{
r[++rr].sum = A;
r[rr].state = state;
return;
}
rdfs(k+1,A,state);
rdfs(k+1,A+a[k],state|(1<<k));
rdfs(k+1,A-a[k],state|(1<<k));
}
bool cmpl (AAA x,AAA y)
{
return x.sum < y.sum;
}
bool cmpr (AAA x,AAA y)
{
return x.sum < y.sum;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)scanf("%d",a+i);
ldfs(1,0,0);
rdfs(n/2+1,0,0);
sort(l+1,l+1+ll,cmpl);
sort(r+1,r+1+rr,cmpr);
int p = 1;
for(int i = 1;i <= ll;i++)
{
while(r[p].sum < l[i].sum && p < rr)p++;
//下面直接找的是 r[p].sum == l[i].sum因为爆搜将很多一样的情况重复计算了
//比如一种情况A B 集合里的元素完全互换所以直接找相等的也是可以的
if(r[p].sum == l[i].sum)
{
//可能有多个相等的所以要往后找一下,但是下一个l[i+1].sum可能和 l[i].sum一样
//所以指针p不能动用t代替
int t = p;
while(r[t].sum == l[i].sum)
{
if(!vis[r[t].state | l[i].state])//检查S集合是否已计算过
{
vis[r[t].state | l[i].state] = 1;
ans++;
}
t++;
}
}
}
printf("%d",ans-1);//减去一个空集
return 0;
}
例三
[蓝桥杯 2023 省 A] 买瓜
题目描述
小蓝正在一个瓜推上买瓜。瓜推上共有 \(n\) 个瓜,每个瓜的重量为 \(A_i\)。小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。
小蓝希望买到的瓜的重量的和恰好为 \(m\)。
请问小蓝至少要劈多少个瓜才能买到重量恰好为 \(m\) 的瓜。如果无论怎样小蓝都无法得到总重恰好为 \(m\) 的瓜,请输出 \(-1\)。
输入格式
输入的第一行包含两个整数 \(n,m\),用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。
第二行包含 \(n\) 个整数 \(A_i\),相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。
输出格式
输出一行包含一个整数表示答案。
样例 #1
样例输入 #1
3 10
1 3 13
样例输出 #1
2
提示
【评测用例规模与约定】
对于 \(20 \%\) 的评测用例,\(n \leq 10\);
对于 \(60 \%\) 的评测用例,\(n \leq 20\);
对于所有评测用例,\(1 \leq n \leq 30\),\(1 \leq A_i \leq 10^9\),\(1 \leq m \leq 10^9\)。
这题双向搜索好想
但是最后答案的合并双指针和unordered_map都会超时
$3^{15} = 14348907 $
$ 2^{24} = 16777216$
只能手写hash
std中用的是拉链法hash
双指针(64):
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
int n,m,a[40],ans = INT_MAX;
PII l[1<<24],r[1<<24];
int ll,rr;
void dfsl(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n/2)
{
l[++ll] = {sum,cnt};
return;
}
dfsl(k+1,sum,cnt);
dfsl(k+1,sum+a[k],cnt);
dfsl(k+1,sum+a[k]/2,cnt+1);
}
void dfsr(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n)
{
r[++rr] = {sum,cnt};
return;
}
dfsr(k+1,sum,cnt);
dfsr(k+1,sum+a[k],cnt);
dfsr(k+1,sum+a[k]/2,cnt+1);
}
bool cmp(PII x,PII y)
{
if(x.first == y.first)return x.second < y.second;
else return x.first < y.first;
}
int main()
{
scanf("%d%d",&n,&m);
m<<=1;
for(int i = 1;i <= n;i++)scanf("%d",a+i),a[i]<<=1;
dfsl(1,0,0);
dfsr(n/2+1,0,0);
sort(l+1,l+1+ll,cmp);
sort(r+1,r+1+rr,cmp);
int p = 1;
for(int i = ll;i >= 1;i--)
{
if(l[i-1].first == l[i].first)continue;
while(r[p].first < m - l[i].first && p < rr)p++;
if(r[p].first == m - l[i].first)
ans = ans < l[i].second + r[p].second ? ans : l[i].second + r[p].second;
}
printf("%d",ans>n?-1:ans);
return 0;
}
unordered_map(76)
#include<bits/stdc++.h>
using namespace std;
int n,m,a[40],ans = INT_MAX;
unordered_map <int,int> mp;
void dfsl(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n/2)
{
int t = mp[sum];
if(t)mp[sum] = min(t,cnt);
else mp[sum] = cnt;
return;
}
dfsl(k+1,sum,cnt);
dfsl(k+1,sum+a[k],cnt);
dfsl(k+1,sum+a[k]/2,cnt+1);
}
void dfsr(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n)
{
int t = mp[m-sum];
if(t)
ans = min(ans,cnt+t);
return;
}
dfsr(k+1,sum,cnt);
dfsr(k+1,sum+a[k],cnt);
dfsr(k+1,sum+a[k]/2,cnt+1);
}
int main()
{
scanf("%d%d",&n,&m);
m<<=1;
for(int i = 1;i <= n;i++)scanf("%d",a+i),a[i]<<=1;
dfsl(1,0,0);
dfsr(n/2+1,0,0);
printf("%d",ans<=n?ans:-1);
putchar('\n');
system("pause");
return 0;
}
std(hash):
#include<bits/stdc++.h>
using namespace std;
int n,m,a[40],ans = INT_MAX;
const int P = 1<<24;
int h[P],ne[P],keys[P],val[P],idx;
void insert(int k,int v)
{
int id = (k%P+P)%P;
keys[++idx] = k,val[idx] = v,ne[idx] = h[id],h[id] = idx;
}
int search(int x)
{
int t = INT_MAX;
for(int i = h[(x%P+P)%P];i;i = ne[i])
{
if(keys[i] == x)t = min(t,val[i]);
}
if(t == INT_MAX)return -1;
else return t;
}
void dfsl(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n/2)
{
insert(sum,cnt);
return;
}
dfsl(k+1,sum,cnt);
dfsl(k+1,sum+a[k],cnt);
dfsl(k+1,sum+a[k]/2,cnt+1);
}
void dfsr(int k,int sum,int cnt)
{
if(cnt > ans)return;
if(sum == m)
{
ans = min(ans,cnt);
return;
}
if(sum > m)return;
if(k > n)
{
int t = search(m-sum);
if(t != -1)ans = min(ans,t + cnt);
return;
}
dfsr(k+1,sum,cnt);
dfsr(k+1,sum+a[k],cnt);
dfsr(k+1,sum+a[k]/2,cnt+1);
}
int main()
{
scanf("%d%d",&n,&m);
m<<=1;
for(int i = 1;i <= n;i++)scanf("%d",a+i),a[i]<<=1;
dfsl(1,0,0);
dfsr(n/2+1,0,0);
printf("%d",ans<=n?ans:-1);
return 0;
}

浙公网安备 33010602011771号