算法复习——二分(二分查找,stl lower_bound()、upper_bound()木棒切割,快速幂)

1.二分查找

①最基础的快乐查找,需要注意的是如果用mid=(left+right)/2,如果right是int上限一半还多,那么可能导致溢出,使用mid=left+(right-left)/2可以代替以避免溢出哦。

②探讨一个新问题:递增序列a是可以重复的,对于想要查找的数字x,求出序列中第一个大于等于x的元素位置l以及第一个大于x的元素位置r,也就是说,x在该序列中的存在区间是[L,R).

1).求l.设当前二分区间为[left,right].如果a[mid]>=x,那么第一个大于等于x的元素一定在mid或者mid左侧。令right=mid.与①的传统做法只有一点点不一样。注意循环条件应该为left<right.二分初始区间为[0,n](假设该元素存在,如果这个元素比所有数都大,就应该返回n)

2).求r是与1类似的。

3).可以考虑用一下c++stl里的lower_bound()和upper_bound().

其中lower_bound(first,last,val)函数在[first,last)进行二分查找返回大于或等于val的第一个元素的位置,如果是数组,返回该位置指针,若没有则返回last的位置。upper_bound()与lower_bound()相似,查找的是第一个大于val的元素的位置。举个栗子0v0:数组a={1,2,4,4,4,5,7,9,9,10,13,17},想查找4第一次出现位置,lower_bound(a,a+n,4)-a,返回值是2.

2.木棒切割问题

以poj1064为例,传送门:http://poj.org/problem?id=1064

题意:给出n条木棒长度,想切割k条相同长度的木棒,求这个长度的max.

思路:如果长度L越大,那么可以得到的k就越小。题目已经给出了k,来二分L,根据当前的L得出的k'与k的大小关系来判断。比如,如果当前的k'大于k,说明这个L取小了。

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<iostream>
#include<map>
#include<vector>
#include<set>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxv=1e4+4;
const double eps=1e-6;

double c[maxv];
int n,k;

int num(double x)
{
	int co=0;
	for(int i=0;i<n;i++)
	{
		co+=(int)(c[i]/x);
	}
	return co;
}

int main()
{
	cin>>n>>k;
	double left=0,right=0,mid;
	for(int i=0;i<n;i++)
	{
		cin>>c[i];
		right=max(right,c[i]);
	}
	while(right-left>=eps)
	{
		mid=(left+right)/2;
		if(num(mid)>=k)
			left=mid;
		else
			right=mid;
	}
	if((int)(right*1000)%10>=5)
		right-=0.005;
	printf("%.2lf\n",right);
	return 0;
}

3.快速幂

①比如,想要算a^b%m,当abm都很大的时候,显然暴力循环是不行的。

而聪明机智的快速幂基于二分的思想:

1)如果b是奇数,那么a^b=a^(b-1)*a

2)如果b是偶数,那么a^b=a^(b/2)*a^(b/2)

可以看出,奇数偶数情况可以相互转换,那么最后必然可以把b变成0,那肯定就是1,这显然是递归的思想,代码如下:

typedef long long ll;

ll binary_pow(ll a,ll b,ll m)
{
	if(b==0)
		return 1;
	if(b%2!=0)
		return a*binary_pow(a,b-1,m)%m;
	else
	{
		ll mul=binary_pow(a,b/2,m);
		return mul*mul%m;
	}
}

ps:b%2==0时直接两个函数相乘的写法会调用两次函数,导致复杂度上升成O(b),而使用中间值mul,是O(logb)

②迭代写法。

通过二进制来实现。把b写成二进制,那么就是若干个二次幂的和。那么a^b可以表示成若干个a的2次幂相乘,且前一项总是等于后一项的平方。

1)初始化ans=1

2)判断b的二进制末尾是不是1,是1那就是奇数,令ans*a

3)令a平方,将b/2(右移一位)

4)返回2),b=0退出

ll quick_pow(ll a,ll b,ll m)
{
	ll ans=1;
	while(b>0)
	{
		if(b&1)
		{
			ans=ans*a%m;
		}
		a=a*a%m;
		b>>=1;
	}
	return ans;
}

③矩阵快速幂。

以poj3070为例,传送门:http://poj.org/problem?id=3070  玩一下矩阵快速幂。

题意:求斐波那契数列第n项对10000取余的结果,n<=10^16

思路:参考了https://blog.csdn.net/ccf15068475758/article/details/52846726题解,使用矩阵乘法求。

代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<iostream>
#include<map>
#include<vector>
#include<set>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
const int mod=1e4;
typedef long long ll;

struct node
{
	int v[3][3];
	int m,l;
}a[10];

/*ll binary_pow(ll a,ll b,ll m)
{
	if(b==0)
		return 1;
	if(b%2!=0)
		return a*binary_pow(a,b-1,m)%m;
	else
	{
		ll mul=binary_pow(a,b/2,m);
		return mul*mul%m;
	}
}

ll quick_pow(ll a,ll b,ll m)
{
	ll ans=1;
	while(b>0)
	{
		if(b&1)
		{
			ans=ans*a%m;
		}
		a=a*a%m;
		b>>=1;
	}
	return ans;
}*/

int n;

node get_mul(node a,node b)
{
	node c;
	c.m=a.m;
	c.l=b.l;
	for(int i=1;i<=c.m;i++)
	{
		for(int j=1;j<=c.l;j++)
		{
			c.v[i][j]=0;
			for(int k=1;k<=a.l;k++)
			{
				c.v[i][j]=(c.v[i][j]+a.v[i][k]*b.v[k][j])%mod;
			}
		}
	}
	return c;
}

int main()
{
	while(scanf("%d",&n))
	{
		if(n==-1)
			return 0;
		if(n==0)
		{
			printf("0\n");
			continue;
		}
		a[0].m=a[0].l=2;
		a[0].v[1][1]=a[0].v[1][2]=a[0].v[2][1]=1;
		a[0].v[2][2]=0;
		a[1].m=a[1].l=2;
		a[1].v[1][1]=a[1].v[2][2]=1;
		a[1].v[1][2]=a[1].v[2][1]=0;
		a[2].m=2,a[2].l=1;
		a[2].v[1][1]=1;
		a[2].v[2][1]=0;
		n--;
		while(n)
		{
			if(n&1)
				a[1]=get_mul(a[0],a[1]);
			a[0]=get_mul(a[0],a[0]);
			n>>=1;
		}
		a[1]=get_mul(a[1],a[2]);
		printf("%d\n",a[1].v[1][1]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39396954/article/details/80957222
今日推荐