暴搜无疑....
首先考虑纯暴搜......
考虑每一个数:
- 选在左边集合
- 选在右边集合
- 不选
一共三种情况,用一个数组记录搜到的答案,所以暴搜是3^N的复杂度...直接死亡
于是讲折半暴搜....
把区间分为两半,对每一半进行dfs,用两个数组(vector)分别记录答案,于是复杂度就是3^(n/2)*2,在n<=20的情况下,能接受。
但是
如果这么简单,那就不是老师找的紫题的风格了....
存储了两个区间的数组,怎么统计答案呢?
话说折半暴搜的难点就在于统计答案了吧.....
对于每个半区间进行排序,然后的暴力?
那不可能,超时不说,还会有重复的记录....
重复是因为:同时可能有一个数存在于左右两个区间中....很尴尬....
这里是这一题的关键,怎么去重。
这里使用了状压的思想,对于每一个搜到底的状态,我们记录一个01串,看哪些数被使用过,然后在统计答案的时候使用vis判重。
这个vis真的是太妙了!!!!
把两个数的状态或起来,看有没有重复使用的状态
(太神了)
if(!vis[a[l].id|b[r].id]) { ans++; vis[a[l].id|b[r].id]=1; }
于是,剩下的就是要在数组中寻找那个关键点pos了.
因为排序后数组单调,所以找到这个pos之后,后面所有的答案都可能符合,只要去重就行了。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=22; int n; int ans; int w[maxn<<1],vis[1<<maxn]; struct node { int sum,id; }; int cnta,cntb; node a[1<<maxn]; node b[1<<maxn]; int mid; void dfs1(int now,int sum,int id) { if(now==mid+1) { a[++cnta].sum=sum; a[cnta].id=id; return; } { dfs1(now+1,sum,id); dfs1(now+1,sum+w[now],id+(1<<(now-1))); dfs1(now+1,sum-w[now],id+(1<<(now-1))); } } void dfs2(int now,int sum,int id) { if(now==n+1) { b[++cntb].sum=sum; b[cntb].id=id; return; } { dfs2(now+1,sum,id); dfs2(now+1,sum+w[now],id+(1<<(now-1))); dfs2(now+1,sum-w[now],id+(1<<(now-1))); } } bool cmp1(node a,node b) { return a.sum<b.sum; } bool cmp2(node a,node b) { return a.sum>b.sum; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&w[i]); } mid=n>>1; dfs1(1,0,0); dfs2(mid+1,0,0); sort(a+1,a+cnta+1,cmp1); sort(b+1,b+cntb+1,cmp2); int l=1,r=1; while(l<=cnta&&r<=cntb) { while(r<=cntb&&-a[l].sum<b[r].sum)r++; int pos=r; while(r<=cntb&&-a[l].sum==b[r].sum) { if(!vis[a[l].id|b[r].id]) { ans++; vis[a[l].id|b[r].id]=1; } r++; } if(l<cnta&&a[l].sum==a[l+1].sum) r=pos; l++; } printf("%d",ans-1); return 0; }
(完)