【折半枚举】

折半枚举

折半枚举是一种很好用的枚举方法,比如有时集合过大无法全部搜索,但刚好只需要他们的和或其他可以处理出的东西,就可以一半一半搜


题1

4 Values whose Sum is 0 传送门:POJ2785 此题就是要求 a+b+c+d=0 的个数 那么 a+b=-(c+d) 只与和有关,所以先把 c+d 的和预处理并排序,当 a+b 值一定时,显然 c+d 值也一定,所以只要二分出 满足c+d=-(a+b) 的 c+d 的个数即可 很容易的样子 以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
int n,ans;;
int a[4005],b[4005],c[4005],d[4005],f[16000005];
int main(){
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i]>>d[i];
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
f[(i-1)*n+j]=c[i]+d[j];
sort(f+1,f+n*n+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
int F=-(a[i]+b[j]);
ans+=upper_bound(f+1,f+n*n+1,F)-lower_bound(f+1,f+n*n+1,F);
}
cout<<ans;
return 0;
}

题2 (NOIP PJ 的题目)

传送门:CodeVS5624 可以算是一个折半枚举吧 显然一次处理完$x_a,x_b,x_c,x_d$时间不支持 用 $ans^{xy}$记录 数x(注意不是第x个)在位置y上出现的次数 那么枚举差值 i , 再枚举 $x_a$ 则 : $x_b=x_a+i*2;$ $x_c>x_b+i*6=x_a+i*8;$ $x_d=x_c+i;$ 换句话说 $x_b$ 已确定下来 然后再来研究一下 $x_c,x_d$$x_c$ 确定下来后,$x_d$ 也确定下来 并且随着 $x_a$ 的增大 $x_c$ 的范围单调递减(反过来就是随着 $x_a$ 的增大,$x_c$ 的范围单调递增) 于是就可以利用这一性质求出 $sum=\sum(f[x_c]*f[x_d])$ 然后就能累加到 $ans_{x_a1} 和 ans_{x_b2}$ 上 同理累加 $ans_{x_c3} 和 ans_{x_d4}$

所以核心就是每次对前两个或后两个利用单调性进行处理

详见代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,sum,A,B,C,D,a[40005],f[20005],ans[20005][5];
int main(){
cin>>n>>m;
for (int i=1;i<=m;i++){
cin>>a[i];
f[a[i]]++;
}
for (int i=1;i<=2000;i++){
sum=0;
for (A=n-9*i-1;A>=1;A--){
B=A+2*i; C=A+8*i+1; D=A+9*i+1;
sum+=f[C]*f[D];
ans[A][1]+=f[B]*sum;
ans[B][2]+=f[A]*sum;
}
sum=0;
for (C=2+8*i;C<=n;C++){
A=C-8*i-1; B=C-6*i-1; D=C+i;
sum+=f[A]*f[B];
ans[C][3]+=f[D]*sum;
ans[D][4]+=f[C]*sum;
}
}
for (int i=1;i<=m;i++)
cout<<ans[a[i]][1]<<' '<<ans[a[i]][2]<<' '<<ans[a[i]][3]<<' '<<ans[a[i]][4]<<'\n';
return 0;
}