二分法学习总结(5.22)
Posted 未定_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分法学习总结(5.22)相关的知识,希望对你有一定的参考价值。
二分学习心得:
二分法,简单说就是不断缩小解存在的范围,从而求得最优解.。一般通过递归方式查找,查找前一般要先进行排序。
基本模板:
int l=0,r=n;// l为区间下限,r为区间上限
while(l<=r)//根据下面l,r的加一减一情况可能限制l<r
//是否取等取决于区间的变化,只要能让循环终止即可
{
mid=(l+r)>>1;//取中间值
if(judge(mid))//判断区间取大取小
l=mid+1;//是否加一看while限制以及题目要求
else
r=mid-1;//是否减一看while限制以及题目要求
}
不做题真不知道自己哪里不会,做题时老是会被一些细节卡住,不是死循环就是超时,还有关于浮点型数的二分,被精度卡死,(=_=),发现一个特点,如果上面的whilr(l<=r),下面应该需要l=mid+1,r=mid-1;如果上面的while(l<r),下面可能有l=mid,r=mid,不一定适用所有情况,具体再因题而定。判断(judge)函数里的返回语句是否有等号也要注意。时间限制上,除2变成>>1,能用变量就不用数组,cin,cout换成scanf,printf等,加上以下语句
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
或者自己写输入语句:
inline int read() {
register char ch=getchar();
register int x=0;
for(; ch<'0'||ch>'9'; ch=getchar());
for(; ch>='0'&&ch<='9'; ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return x;
}
虽然自己写的输入语句较快,但这不是绝对的,有时可能不如scanf快,还取决于代码的写法。
另外:学到一种简单输出变量值的方式,可以用在检查程序运行过程中变量的变化,更简便的输出这个变量的值。
#define dbg(x) cerr<<#x" = "<<x<<endl;
二分做题总结:
D
题意:a,b,c,d四个数组,从每一个数组中挑一个形成一个组合,求最多有多少个组合数为0.
做的第一道二分题,还不太会用,这个题有点类似a+b+c=s那个题。类似方法比较容易想到a+b存一个数组sum1,-(c+d)存一个数组sum2,然后用二分法找sum1的每个数与sum2里的每个数相同的个数。代码还不太会写,写了好久没运行出来T_T。学习了一些其他人的代码实现方法。
法1:先固定sum1,每次从sum1里取一个数,然后在sum2里二分查找是否存在这个数以及存在几个,累加求和。二分函数实现形式。
void find(int a)
{ int l=0,h=k-1,mid;
while(l<h)
{ mid=(l+h)/2;
if(sum2[mid]<a)
//必须是小于a不能等于,如果等于的话,此时mid是找到的a值,然后l=mid+1再重新计算区间中值,区间变了,少了一部分a解。
l=mid+1;//为了找到从小到大排第一个a值
else h=mid;
//更新h,即使h=mid=a时,l一定会移动到h处,此时h=l=a。
}
while(sum2[l]==a&&l<k)
//如果第一个l不是a,那么后面的一定都不是,反之,如果第一个是,找后面是否有相等的数。
{
ans++;
l++;
}
}
详细代码:
#include<iostream>
#include<algorithm>
using namespace std;
long long int a[4005],b[4005],c[4005],d[4005];
long long int sum1[4005*4005],sum2[4005*4005];
int ans=0,k=0;
void find(int a)
{ int l=0,h=k-1,mid;
while(l<h)
{ mid=(l+h)/2;
if(sum2[mid]<a)
l=mid+1;
else h=mid;
}
while(sum2[l]==a&&l<k)
{
ans++;
l++;
}
}
int main()
{
int n,i,j,l=0;
cin>>n;
for(i=0; i<n; i++)
{
cin>>a[i]>>b[i]>>c[i]>>d[i];
}
for(i=0; i<n; i++)
{ for(j=0; j<n; j++)
{ sum1[k]=a[i]+b[j];
sum2[k]=-c[i]-d[j];
k++;
}
}
sort(sum1,sum1+k);//必须要先排序
sort(sum2,sum2+k);
for(i=0; i<k; i++)
{
find(sum1[i]);
}
cout<<ans<<endl;
return 0;
}
法2
主函数部分和上面一样,find函数换了一种写法,这里的处理方式和法一不同,在找mid的时候找到的a不一定是数组中出现的第一个a,因为处理时只要遇到a循环就停,a不一定是第一个a,而上一个方法通过l,不断更新找到第一个a,所以法2在处理的时候需要mid两边都判断。
void find(int a)
{ int l=0,h=k-1,mid,p=0,i;
while(l<=h)//必须有等号,否则会落掉l=h=mid且sum2[mid]=a这种情况。
{ mid=(l+h)>>1;//移位操作比除以2运行更快
if(sum2[mid]==a)
{
p=1;
break;
}
else if(sum2[mid]<a)
{ l=mid+1;
}
else
{ h=mid-1;
}
}
if(p)
{
for(i=mid-1; i>=0; i--)//判断左边有几个等于a的
{
if(sum2[i]==a)
ans++;
else break;
}
for(i=mid+1; i<k; i++)//判断右边有几个等于a的
{
if(sum2[i]==a)
ans++;
else break;
}
ans++;//加上本身
}
}
法3:(最简单的方法)
参考博客:二分 折半枚举
用upper_bound 和 lower_bound:
upper_bound(begin, end, value);
返回>value的元素的第一个位置。
lower_bound(begin, end, value);
返回>=value的元素的第一个位置。
这样只需要累加个数就行了
#include<iostream>
#include<algorithm>
using namespace std;
long long int a[4005],b[4005],c[4005],d[4005];
long long int sum1[4005*4005],sum2[4005*4005];
int ans=0,k=0;
int main()
{
int n,i,j,l=0;
cin>>n;
for(i=0; i<n; i++)
{
cin>>a[i]>>b[i]>>c[i]>>d[i];
}
for(i=0; i<n; i++)
{ for(j=0; j<n; j++)
{ sum1[k]=a[i]+b[j];
sum2[k]=-c[i]-d[j];
k++;
}
}
sort(sum1,sum1+k);
sort(sum2,sum2+k);
for(i=0; i<k; i++)
{
ans+=(upper_bound(sum1,sum1+k,sum2[i])-lower_bound(sum1,sum1+k,sum2[i]));
}
cout<<ans<<endl;
return 0;
}
H
题意:给了河的长度,石头数量,可删除的石头数量,求解删除指定数量的石头后石头间的最小距离的最大值是多少
二分查找,(一开始我还以为是贪心)
先假设一个距离,这个距离的假设通过二分确定,然后找这个最小距离下可删除的石头个数,不断更新距离直到找到最小距离的最大值。代码解释见下(建议先看代码再看图片)
关于代码几个细节问题:
•find函数里for循环里的判断语句
if(d[i]-p<now)
这里必须是小于不能等于,因为等于最小距离时石头必须保留,留下的石头最小之差才是now
•find函数里的return语句
return res<=m;
这里必须小于等于,因为最多删m个,很好理解,另外res的值还可能包括删除河岸L处的石子
•主函数while(l<=r)
,这里必须有等号,l=r=mid时的情况必须要讨论,其可能是最优解。
•ans=mid
放在右二分的语句里,因为小于等于m的情况都是合理的,从这里选最优解,所以ans要在右二分(l=mid+1)语句里更新。
#include<iostream>
#include<algorithm>
using namespace std;
long long int L,i,j,n,m,d[50005];
long long int l=0,r=0,mid=0,ans;
bool find(int now)
{
int res=0,p=0;//res记录删除的个数,p记录指向石头的前一个石头的位置
for(i=0; i<n; i++)
{
if(d[i]-p<now)//计算指向石头与前一个石头位置的差值,如果小于假设的最小位置,就删石头
res++;
else p=d[i];//如果大于等于假设的最小位置就保留该石头,并更新p,便于计算下一个石头到该石头的距离
}
return res<=m;//最多删m个石头,这里做了一个判断,便于假设距离通过二分更新
}
int main()
{
cin>>L>>n>>m;
for(i=0; i<n; i++)
{ cin>>d[i];
}
d[n]=L;
sort(d,d+n+1);
r=L;
while(l<=r)
{
mid=(l+r)>>1;
if(find(mid))//说明假设距离小了,删的石头少,扩大距离l=mid+1
{ ans=mid;//这时的答案是对的,但不是最优,所以先记录下来,不断更新找到最优解
l=mid+1;
}
else r=mid-1;//假设距离大了,删的石头多了,缩小距离r=mid-1
}
cout<<ans<<endl;
return 0;
}
N
题意:给定一组数据,表示在第几个月存进多少钱,然后给出第 k (1<=k<=12)个月后钱的总数,要求根据这些条件算出月利率(假设月利率是固定不变的)
简单的数学应用题,列出表达式求方程的解即可。本来以为能一次就过的,结果错了好几遍都是时间限制T_T。
时间限制原因:忘记给m数组赋0导致第二个样例不运行;最后一个月,即给出总和的月份存入数组,自定义函数的时候返回值中有带数组的运算,好像挺费时间的,把最后一个月用普通变量就过了。
部分图片正误对比
正确代码
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
using namespace std;
int y[13],i,n,p=1,mm,c[10000];
double m[13]= {0},h;
double l,r,mid;
double f(double a)
{ double sum=0.0;
for(i=y[0]; i<=mm; i++)
{
if(m[i]==0)
sum*=1+a;
else sum=(sum+m[i])*(1+a);
}
return sum-h;
}
int main()
{
while(cin>>n&&n!=-1)
{
memset(m,0,sizeof(m));
for(i=0; i<n; i++)
{ cin>>y[i];
cin>>m[y[i]];
}
cin>>mm>>h;
l=0.0;
r=1.0;
while(1)
{
mid=(l+r)/2.0;
if(fabs(f(mid))<1e-6)
break;
else if(f(mid)>0)
{
r=mid;
}
else
{
l=mid;
}
}
if(p>1)
cout<<endl;
cout<<"Case "<<p++<<": ";
cout<<fixed<<setprecision(5)<<mid<<endl;
}
return 0;
}
X
题意:给你一个数,找前后最近素数的差。
用到了素数表,没看出来怎么用的二分(゚o゚;
练习一下素数判断方法,还有怎么打印素数表,这个代码还有一个地方挺疑惑的,两个自定义的函数for循环里的i如果放在全局变量里,就是函数里不再重新int不知道为啥就runtime,就像下图(部分图片),两个自定义函数的for循环里的i一个int i,一个直接用全局的i,其余的代码都一样,欢迎大神解答疑惑
最后附上正确代码
#include <iostream>
#include <cmath>
using namespace std;
int p[1000001],i,l=0;
bool ss(int n)
{
for(int i=2; i<=sqrt(double(n)); i++)
if(n%i==0)
return 0;
return 1;
}
void b()
{
for(int i=2; i<=1299709; i++)
if(ss(i))
p[l++]=i;
}
int main()
{
b();
int n;
while(cin>>n&&n)
{
for(i=0; i<10000000; i++)
if(p[i]>=n)
break;
if(p[i]==n)
cout<<0<<endl;
else
cout<<p[i]-p[i-1]<<endl;
}
return 0;
}
以上是关于二分法学习总结(5.22)的主要内容,如果未能解决你的问题,请参考以下文章