Meet int the middle--cj集训10.16模拟赛T1

本文探讨了一种子集划分问题的高效求解方法,通过使用meet in the middle策略,将原始的3^n复杂度降低至O(6^(N/2))。文章详细介绍了如何将集合分为左右两部分进行枚举,利用映射和集合来记录可能的子集和,从而快速找到满足条件的子集。
摘要由CSDN通过智能技术生成

题目:
有多少个非空子集,能划分成和相等的两份。
solution:
只想到了 3 n 3^n 3n暴力
其实可以用 m e e t   i n t   t h e   m i d d l e meet\ int\ the\ middle meet int the middle的思想降低复杂度
左边的那些 3 N / 2 3^{N/2} 3N/2枚举分别是不放还是放到第一组还是放到第二组,并记录下来。
右边的 3 N / 2 3^{N/2} 3N/2枚举后,再 2 N / 2 2^{N/2} 2N/2看看左边符合这个值的那些,就行了。
总复杂度 O ( 6 N / 2 ) O(6^{N/2}) O(6N/2)

其实其他的都能想到,但就是降低复杂度的方法以前没有用过
m e e t   i n   t h e   m i d d l e meet\ in\ the\ middle meet in the middle只做过裸题,其实很多时候都可以这样做

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#define maxn 25
using namespace std;
int n,a[maxn],ans,haf,tot;
bool can[(1<<20)+5];
map<int,int> mp;
set<int> s[60000];
set<int>::iterator it;

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

inline void solve(int ss,int lfsm,int rgsm){
	if(!mp.count(rgsm-lfsm)) return;
	int cur=mp[rgsm-lfsm];
	for(it=s[cur].begin();it!=s[cur].end();it++)
		can[ss|(*it)]=true;
	return;
}

void dfs(int now,int t,int cur,int lfsm,int rgsm){
	if(now==t){
		if(t==n) {solve(cur,lfsm,rgsm);return;}
		int cc=lfsm-rgsm;
		if(!mp.count(cc)) mp[cc]=++tot;
		s[mp[cc]].insert(cur);
		return; 
	}
	dfs(now+1,t,cur,lfsm,rgsm);//不放
	dfs(now+1,t,cur|(1<<now),lfsm+a[now+1],rgsm);//放左边
	dfs(now+1,t,cur|(1<<now),lfsm,rgsm+a[now+1]);//放右边
	return; 
}

int main(){
	freopen("subsets.in","r",stdin);
	freopen("subsets.out","w",stdout);
	n=rd();
	for(int i=1;i<=n;i++) a[i]=rd();
	haf=n/2;
	dfs(0,haf,0,0,0);
	dfs(haf,n,0,0,0);
	for(int i=1;i<(1<<n);i++)
		if(can[i]) ++ans;
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值