meet in the middle 折半搜索 刷题记录
复杂度分析
假设本来是n层,本来复杂度是O(2^n),如果meet in middle那就是n/2层,那复杂度变为O( 2^(n/2) ),跟原来的复杂度相比就相当于开了个方
比如如果n=40那爆搜2^40肯定T飞,那用meet in middle的话就是2^20就可做了。
洛谷P2962 [USACO09NOV]灯Lights
- 灯只有35个,用二进制可以表示所有灯的状态,于是考虑搜索
- 1表示该灯是亮的,0表示是灭的
- 把某一个等和与他相邻的灯的位都置1表示该灯位置的开关,用 li 数组表示,按下开关 i 就相当于异或 li[i]
- map存前半部分能到达的状态下按的最少开关数
- 先爆搜前半部分,更新map,再爆搜后半部分,看其补集是否存在更新答案
- 注意前半部分本来就有不需要按灯的状态,所以cnt一开始都是1,最后ans-2即可
- 基本上抄袭hzwer代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 int n, m, flag=1, ans=1e9; 6 ll x[40], li[40]; 7 ll e=0; 8 map <ll,int> st; 9 10 void dfs(int now, ll d, int cnt) { 11 if(now == n/2+1 && flag) { 12 if( st[d]!=0 ) st[d] = min(st[d], cnt); 13 else st[d] = cnt; 14 return; 15 } 16 if(now == n+1) { 17 if( st[e-d]!=0 ) ans = min(ans, st[e-d]+cnt); 18 return; 19 } 20 dfs(now+1, d^li[now], cnt+1); //按下开关 21 dfs(now+1, d, cnt); 22 } 23 24 int main(){ 25 cin >> n >> m; 26 x[1] = 1; 27 for (int i=2; i<=n; i++) x[i] = x[i-1] << 1 ; 28 int a, b; 29 for (int i=0; i<m; i++) { 30 scanf("%d%d", &a, &b); 31 li[a] ^= x[b]; 32 li[b] ^= x[a]; 33 } 34 for (int i=1; i<=n; i++) { li[i] ^= x[i]; e ^= x[i]; } 35 dfs(1,0,1); 36 flag = 0; 37 dfs(n/2+1,0,1); 38 cout << ans-2 << endl; 39 return 0; 40 }
洛谷P4799 [CEOI2015 Day2]世界冰球锦标赛
- 搜后一半的时候需要知道前一半有多少值小于tmp
- 把前一半的值用数组保存起来,排个序,upperbound就行了
- longlong 打成int调了好久,为什么还是会犯这种很傻的错误呢,害
- 代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 ll n,tot,flag=1,ans=0,mid,id=0; 6 ll c[50]; 7 ll mj[1100000]; 8 9 void dfs(ll now,ll s){ 10 if(s>tot) return; 11 if(now==mid && flag){ 12 mj[++id]=s; 13 return; 14 } 15 if(now==n+1){ 16 ll tmp=tot-s; 17 ans+=( upper_bound(mj+1, mj+id+1, tmp)-mj-1 ); 18 return; 19 } 20 dfs(now+1,s+c[now]); 21 dfs(now+1,s); 22 } 23 24 int main(){ 25 cin>>n>>tot; 26 mid=n/2+1; 27 for (int i=1; i<=n; i++) scanf("%lld",&c[i]); 28 dfs(1,0); 29 sort(mj+1,mj+id+1); 30 flag=0; 31 dfs(mid,0); 32 cout<<ans<<endl; 33 return 0; 34 }