二分(三分)+快速幂

 

之前学习的二分,现在感觉突然理解许多,补一下总结
首先,二分能够解决什么样的问题呢,个人认为,二分能够快速解决已经知道答案范围(线性)但是不知道确切答案的问题,例如在一个有序序列中查找某一元素出现的(最早,最晚)位置,求某单调(或在给定区间上单调)函数的零点,最大化最小值或者最小化最大值等等
二分的模板大体如下:

int l=x;//x表示元素可能出现的最小(最左边)的情况
int r=y;//y表示元素可能出现的最大(最右边)的情况
int ans,mid;
while(i<=l)
{
	mid=(l+r)/2;  //二分
	if(check(mid))//判断中点的情况
	{
		l=mid+1;
		ans=mid;  //或者ans=max(ans,mid)等,ans用于保存答案,如果需要所有可行答案中的最大值或者最小值加上max或者min即可
	}
	else
	{
		r=mid-1;
	}
}

总体来讲二分的思想还是比较简单的,但是问题的难点经常不是考虑不到二分,而是考虑到二分却不知道如何判断要查找的元素在mid的情况下能否可行,即如何编写check(mid)函数。这个函数的编写往往要仔细考虑要查找元素的特征(以及脑洞)。
比如经常出现的最大化最小值和最小化最大值等,那些放牛的等问题判断起来还是比较容易的,就从开始一个一个按照条件放,如果能行就能行。但是更多的问题还是比较灵活的,比如POJ - 3104

题意大概是一个人冬天洗衣服想要让衣服尽快的干问最少需要多少时间。
这个我们很容易确定所花最长时间是水分最多的衣服所含有的水分(不用那个烘干器自然风干),最短时间为0,但是给定一个时间,我们怎么判断这个时间内可不可以将衣服晾干呢?
这就需要我们进行一个转换,我们不妨这样想:这些衣服先晾了mid分钟,然后再将还没有干的全部用烘干器烘干,烘干器只能烘mid次,每次烘干k-1的水分。
乍看好像有些无厘头,其实仔细一想很简单,我们只不过把原来一起做的事情拆开来分析而已(表示不好想到)。
这样的话我们遍历每件衣服,如果它没有自己晾干就得用烘干器,如果在mid次以内全部烘干了就可以,如果没有就不行。
需要注意一点的是如果原本烘干器是个假的烘干器(每分钟烘干的速度和晾衣服的速度一样),那么只处理晾后的,就不要用烘干器再处理了(烘干器每次处理0,不注意这点的话可能会re,我re了好多次才注意到)。

附AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,k;
int a[100100];

bool cmp(int a,int b)
{
	return a>b;
}
bool check(int time)
{
	int tmp=time;
	int t;
	for(int i=0;i<n;i++)
	{
		t=a[i]-time;
		if(t>0)
		{
			if(k==0)
			return false;
			tmp-=t/k;
			if(t%k!=0)
			tmp--;
		}
		if(tmp<0)
		return false;
	}
	return true;
}
int main()
{
	int tmp;
	memset(a,0,sizeof(a));
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	scanf("%d",&k);
	k--;
	sort(a,a+n,cmp);
	int l=0,r=a[0];
	int ans=0,mid;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(check(mid))
		{
			r=mid-1;
			ans=mid;
		}
		else
		{
			l=mid+1;
		}
	}
	printf("%d",ans);
	return 0;
}

除此之外二分法快速幂也经常使用,必须熟记(虽然我觉得是一个递归)
快速幂板子(非递归写法):

long long qucik_pow(long long a,long long n,long long m)//求a^n%m
{
	long long ans=1;
	while(n)
	{
		if(n&1)	ans=(ans*a)%m;
		a=a*a%m;
		n>>=1;
	}
	return ans%m;
}

还有递归写法(有助于理解快速幂,但是不常用)

long long quick_pow(long long a,long long n,long long m)
{
	if(n==1) return a;
	if(n%2==0)
	{
		long long t=quick_pow(a,n/2,m);
		return t*t%m;
	}
	else
	{
		long long t=quick_pow(a,b/2,m);
		t=t*t%m;
		t=t*a%m;
		return t;
	}
}

三分的话每太用过,只是见过用来求凸凹函数的极值点
思想就是先将区间二分,在将其中一个区间二分,然后比较两个点的大小,哪个距离极值点远就将哪一边更新,应该是可以根据凹凸函数的性质进行证明的(我比较懒就不证明了,有时间再证吧),附一下板子:

while(left+eps<right)
{
	midl=(left+right)/2;
	midr=(midl+right)/2;
	if(f(midl)<f(midr))
	{
		right=midr;
	}
	else
	{
		left=midl;
	}
}
原文地址:https://www.cnblogs.com/qgmzbry/p/10662103.html