折半枚举

折半枚举其实就是一个思想,并不能具体化为一个模板。

那么折半枚举什么时候用?当你发现必须要枚举时,看看时间限制居然刚好把你的暴力枚举卡掉时或者把你的暴力枚举时间砍一半之后才能过的时间,(处理数据竞赛中10^8=1秒详细请看-》https://blog.csdn.net/qq_40763929/article/details/86726906)你就可以想想如何缩短枚举时间,如果能用上折半枚举的话,同样是枚举,就会比原来的枚举缩短一半的时间!

所谓折半?就是对半砍,让你枚举前一半的部分,然后枚举后一半的部分,得到结果!

来看几道例题:

例题一
poj2785
Description

The SUM problem can be formulated as follows: given four lists A, B, C, D of integer values, compute how many quadruplet (a, b, c, d ) ∈ A x B x C x D are such that a + b + c + d = 0 . In the following, we assume that all lists have the same size n .
Input

The first line of the input file contains the size of the lists n (this value can be as large as 4000). We then have n lines containing four integer values (with absolute value as large as 228 ) that belong respectively to A, B, C and D .
Output

For each input file, your program has to write the number quadruplets whose sum is zero.
Sample Input

6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45

Sample Output

5

题意就是让你在n个四元组找出四个四元组,每个四元组找一个数出来,组成一个新的四元组,它的四个元素之和为0.
你想nnnn直接枚举,n最大为4103不用想了,爆!
对半砍看看是多少,n2妈妈再也不用担心我TL了
AC代码:

#include<bits/stdc++.h>
using namespace std;
#define MAX 4000
int n;
int a[MAX+5],b[MAX+5],c[MAX+5],d[MAX+5];
int A[MAX*MAX+5]; 
void solve()
{
	int ans=0;
	int sum=0;
	//折半枚举
	 for(int i=0;i<n;i++){
	 	for(int j=0;j<n;j++){
	 		A[i*n+j]=a[i]+b[j];
		 }
	 }//记录a,b之和 
	 sort(A,A+n*n);//排序为了方便计算个数,降低时间复杂度 
	 for(int i=0;i<n;i++){
	 	for(int j=0;j<n;j++){
	 		sum=-(c[i]+d[j]);
	 		ans+=(upper_bound(A,A+n*n,sum)-A)-(lower_bound(A,A+n*n,sum)-A);
		}
	 }
	 printf("%d\n",ans);
	 return;
}
int main(void)
{
	scanf("%d",&n);
	int *p=a;
	for(int i=0;i<n;i++){ 
		for(int j=0;j<4;j++){
			if(j==0) p=a+i;
		else if(j==1) p=b+i;
		else if(j==2) p=c+i;
		else p=d+i;
		scanf("%d",p);
		}
		
	}
	solve(); 
	return 0;
}

例题二:(博主认为也是折半枚举,但似乎有点牵强,但思想我认为还是有的)
洛谷P1627中位数

题目描述
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。

输入格式
第一行为两个正整数n和b,第二行为1~n的排列。

【数据规模】

对于30%的数据中,满足n≤100;

对于60%的数据中,满足n≤1000;

对于100%的数据中,满足n≤100000,1≤b≤n。

输出格式
输出一个整数,即中位数为b的连续子序列个数。

输入输出样例
输入 #1

7 4
5 7 2 4 3 1 6 

输出 #1

4

AC代码:

//折半枚举应该是
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main(void)
{
	int t,n,mid;
	map<int,int> s;
	//scanf("%d",&t);
	//while(t--)
	//{
		int m;
		scanf("%d %d",&n,&mid);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if(a[i]==mid)
			m=i;//记录要求的数的位置 
		}//m是唯一的 故可对半分开枚举 
		int count=0;
		//中位数的后的区域
		int sumh=0;
		for(int i=m;i<=n;i++){
			if(a[i]>mid) sumh++;
			if(a[i]<mid) sumh--;
			s[sumh]++;
		}
		//中位数前的区域 
		int sumq=0;
		//注意枚举顺序: 1		<-	m 	->   n   因为中位数的区间是连续的 
		for(int i=m;i>=1;i--){
			if(a[i]>mid) sumq++;
			if(a[i]<mid) sumq--;
			//a[m]是中位数:sumq+sumh=0 
			if(s.count(0-sumq)>0)
			{
				count+=s[0-sumq];
			}
		}
		printf("%d\n",count);
	//	s.clear();//删除所有元素 
	//}
}

例题三:
牛客暑假训练营-Knapsack Cryptosystem
来源:牛客网

题目描述
Amy asks Mr. B problem D. Please help Mr. B to solve the following problem.

Amy wants to crack Merkle–Hellman knapsack cryptosystem. Please help it.

Given an array {ai} with length n, and the sum s.
Please find a subset of {ai}, such that the sum of the subset is s.

For more details about Merkle–Hellman knapsack cryptosystem Please read
https://en.wikipedia.org/wiki/Merkle%E2%80%93Hellman_knapsack_cryptosystem
https://blog.nowcoder.net/n/66ec16042de7421ea87619a72683f807
Because of some reason, you might not be able to open Wikipedia.
Whether you read it or not, this problem is solvable.
输入描述:
The first line contains two integers, which are n(1 <= n <= 36) and s(0 <= s < 9 * 1018)
The second line contains n integers, which are {ai}(0 < ai < 2 * 1017).

{ai} is generated like in the Merkle–Hellman knapsack cryptosystem, so there exists a solution and the solution is unique.
Also, according to the algorithm, for any subset sum s, if there exists a solution, then the solution is unique.
输出描述:
Output a 01 sequence.

If the i-th digit is 1, then ai is in the subset.
If the i-th digit is 0, then ai is not in the subset.
示例1

8 1129
295 592 301 14 28 353 120 236

输出:

01100001

2n枚举(n<=36)必然超时,把n砍一半呢?
look -> 218

#include<bits/stdc++.h>
using namespace std;
#define ll long long int 
ll a[40];
int main(void)
{
	int n;
	ll s;
	map<ll,string> m1;
	map<ll,string> m2;
	scanf("%d %lld",&n,&s);
	for(int i=0;i<n;i++)
	scanf("%lld",&a[i]);
	ll sum=0;
	for(int i=0;i<1<<(n/2);i++){//枚举前1-2^18的状态,2^18看成二进制1000000000000000 
		string s1;
		 sum=0;
		 for(int j=0;j<n/2;j++){//看每一位的情况 
		 	if((i>>j)&1)//二进制 判断当前位是不是1 
			 {
			 	s1.push_back('1');
				 sum+=a[j]; 
			 }
			 else
			 s1.push_back('0'); 
		 }
		 m1[sum]=s1;//记录 
	}
	//for(int i=(1<<(n/2));i<(1<<n);i++){//这样枚举会漏了000000000 11111111等状态
	for(int i=0;i<1<<(n-n/2);i++){
	    string s2;
		 sum=0;
		 for(int j=n/2;j<n;j++){//看每一位的情况 
		 	if((i>>(j-n/2))&1)
			 {
			 	s2.push_back('1');
				 sum+=a[j]; 
			 }
			 else
			 s2.push_back('0'); 
		 }
		 m2[sum]=s2;//记录 
		 if(m1.count(s-sum)>0)
		 {
		 	cout<<m1[s-sum];
		 	cout<<s2<<endl;
		 	break;
		 }
	}
	return 0;
}

有没有发现,折半枚举,查找前一半的结果,是使用了c++的map容器的,也不是必须要,你有时候可以用数组代替,只要你能在后半部分枚举时要使用前半部分枚举的结果时能直接拿出来就不用再次枚举就行了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值