我的天,折半搜尋(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;
}