折半搜索
P3067 [USACO12OPEN]Balanced Cow Subsets G
题目大意
给你n (n<=20) 个数,从中任意选择一些数分到两组中使得两组数之和相同
(1<= a <= 1e18)
折半搜索
因为n的值较小就考虑搜索解决,每个数通常有三个状态:
- 分到左边
- 分到右边
- 不选择
这个问题简单的来说就是:a+b = c+d
我们移个项就变成了:a-c = d-b
如果是暴力搜索三种状态复杂度是O(3\(^{n}\))>1e9,这样明显会TLE
但是如果用折半搜索,复杂度为O(3\(^{n/2}\))就不会超时了
搜索的思路就是:先搜索前一半,每次到达终点的时候记录一下状态(二进制表示)
ans是当前的和,st为状态
void dfs1(int u, int ans, int st) {
if (u >= n / 2) {
v[ans].push_back(st);
return;
}
dfs1(u+1,ans+a[u],(st<<1)|1);
dfs1(u+1,ans-a[u],(st<<1)|1);
dfs1(u+1,ans,(st<<1));
}
然后搜索另一半,对状态进行记录和匹配
之所以匹配-ans是因为要满足:a-c = d-b\(\rightarrow\)a+b-c-d = 0
也就是ans1+ans2 = 0
void dfs2(int u,int ans,int st){
if(u>=n){
for(auto i:v[-ans]){
if(!vis[(i<<10)|st]){
vis[(i<<10)|st] = 1;
key++;
}
}
return;
}
dfs2(u+1,ans+a[u],(st<<1)|1);
dfs2(u+1,ans-a[u],(st<<1)|1);
dfs2(u+1,ans,(st<<1));
}
状态表示如果前一半选13,后一般选57则由如下二进制:
1010000000000 | 1010 $\rightarrow$1010000001010
同时记录当前状态,以免重复搜索
代码实现:
# include<iostream>
# include<bits/stdc++.h>
using namespace std;
# define int long long
# define endl "\n"
const int N = 1 << 21, inf = 1e9 + 7;
unordered_map<int, vector<int> > v;
bool vis[N];
int a[100];
int n,key;
void dfs1(int u, int ans, int st) {
if (u >= n / 2) {
v[ans].push_back(st);
return;
}
dfs1(u+1,ans+a[u],(st<<1)|1);
dfs1(u+1,ans-a[u],(st<<1)|1);
dfs1(u+1,ans,(st<<1));
}
void dfs2(int u,int ans,int st){
if(u>=n){
for(auto i:v[-ans]){
if(!vis[(i<<10)|st]){
vis[(i<<10)|st] = 1;
key++;
}
}
return;
}
dfs2(u+1,ans+a[u],(st<<1)|1);
dfs2(u+1,ans-a[u],(st<<1)|1);
dfs2(u+1,ans,(st<<1));
}
void solve() {
cin >> n;
for (int i = 0; i < n; ++i) cin >> a[i];
dfs1(0,0,0);
dfs2(n/2,0,0);
cout<<key-1<<endl;
}
int tt;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
tt = 1;
// cin >> tt;
while (tt--)solve();
return 0;
}

浙公网安备 33010602011771号