折半搜索

P3067 [USACO12OPEN]Balanced Cow Subsets G


题目大意

给你n (n<=20) 个数,从中任意选择一些数分到两组中使得两组数之和相同
(1<= a <= 1e18)


折半搜索

因为n的值较小就考虑搜索解决,每个数通常有三个状态:

  1. 分到左边
  2. 分到右边
  3. 不选择

这个问题简单的来说就是: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;
}

其他题目

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

posted @ 2022-10-31 21:10  empty_y  阅读(41)  评论(0)    收藏  举报