P3067 [USACO12OPEN] Balanced Cow Subsets G

袍蚤發表於2024-09-15

我的天,折半搜尋(meet in the middle),依稀記得我學過,但是真的不記得。。。。

從狀態圖上起點和終點同時開始進行寬度/深度優先搜尋,如果發現相遇了,那麼可以認為是獲得了可行解。

這道題,每一個元素會有3種狀態,分別是在第一個集合或者第二個集合亦或者不在集合中。如果直接暴力去搜的話,時間複雜度是三次方級別的,不能被接受。
所以考慮折半搜尋,就可以直接把複雜度上的指數砍半,就可以過掉這道題了。

折半搜尋實現

#include<bits/stdc++.h>
#define int long long
#define pb push_back

using namespace std;

const int N=25,M=2e6+100;

int n;
int ans[M];
int a[M];
int s,tot;

vector<int> p[M];
map<int,int> b;

void dfs1(int x,int sum,int now)
{
	//對前一半進行搜尋,now->對取了的數進行狀態壓縮
	if(x>n/2)
	{
		if(b[sum]==0)	b[sum]=++tot;//李三華
		p[b[sum]].pb(now);
		return; 
	} 
	dfs1(x+1,sum+a[x],now|(1<<(x-1)));
	dfs1(x+1,sum-a[x],now|(1<<(x-1)));
	dfs1(x+1,sum,now);
	//分三種情況討論,要麼在左,要麼在右,要麼都不在 
}

void dfs2(int x,int sum,int now)
{
	//對後一半進行搜尋
	if(x>n)
	{
		int t=b[sum];
		if(t!=0)
			for(int i=0;i<p[t].size();i++)
				ans[p[t][i]|now]=1;
			//對於每一種可能的組合,將值賦為1,
			//因為題目中要求的方案數為取數的方案數而不是分數的方案數
			//因此不是+1而是=1
		return;
	 } 
	dfs2(x+1,sum+a[x],now|(1<<(x-1)));
	dfs2(x+1,sum-a[x],now|(1<<(x-1)));
	dfs2(x+1,sum,now);
 } 

signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)	cin>>a[i];
	dfs1(1,0,0),dfs2(n/2+1,0,0);
	for(int i=1;i<=(1<<n);i++)	s+=ans[i];
	cout<<s<<endl;
	return 0;
}
//meet in the middle(折半搜尋)

也可以用狀態壓縮實現大部分折半搜尋的題目,這道題也可以用三進位制狀態壓縮去做。

#include<bits/stdc++.h>
#define int long long

using namespace std;

int n;
int B[2],a[22],pow3[12],ans;
bool st[(1<<20+5)];
map<int,vector<int>> f[2];

signed main()
{
	cin>>n;
	B[0]=n/2,B[1]=n-B[0];
	for(int i=0;i<n;i++)	cin>>a[i];
	pow3[0]=1;
	for(int i=1;i<=B[1];i++)	pow3[i]=pow3[i-1]*3;
	for(int t=0;t<=1;t++)
		for(int s=0;s<pow3[B[t]];s++)
		{
			int d=0,S=(1<<B[0])-1;
			if(t==1)	S=((1<<n)-1)-S;
			for(int i=0;i<B[t];i++)
			{
				int v=(s/pow3[i])%3;
				if(v==1)	d+=a[i+t*B[0]];
				else if(v==2)	d-=a[i+t*B[0]];
				else	S-=(1<<(i+t*B[0]));
			}
			f[t][d].push_back(S);	
		}
	for(auto a:f[0])
		for(int b:a.second)
			for(int c:f[1][-a.first])
				if(!st[c|b])	ans++,st[c|b]=true;
	cout<<ans-1<<endl;
	return 0;
}

相關文章