手写二分:
若x在数组a[N]中,查找x在数组中的位置:
int find(int x)
{
int mid,l=0,r=n-1;
while(l<=r)
{
mid=(l+r)/2;
if(a[mid]==x) return mid;
else if(a[mid]>x) r=mid-1;
else l=mid+1;
}
return -1; //如果没有找到,返回-1
}
若x不一定在数组中,查找数组中小于x的数有几个:(利用逐步逼近的方法)
int find(int x)
{
int mid,l=0,r=n-1;
while(r-l>1)
{
mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid;
}
return l+1;
}
二分里面两个重要的函数:
lower_bound: 查找序列中的第一个出现的值大于等于x的位置
upper_bound: 返回的是第一个大于x的位置
使用方法都是: pos=lower_bound(a,a+n,x)-a;
pos=upper_bound(a,a+n,x)-a;
(并且在使用之前要把作用数组sort一次。)
比如:map中已经插入了1,2,2,3,4的话,如果lower_bound(2)的话,返回的2,而upper_bound(2)的话,返回的就是3。
POJ 2785 – 4 Values whose Sum is 0
大概题意是有4列n行数,在每列中选择一个数,使得选出的4个数相加和为零。(n<=4000)。如果用枚举的方法,最大的情况大约O(4000*4000*4000*4000)。这样肯定是不行的。所以可以将前两列数和的情况全部枚举出来,后两列数的和的情况全部枚举出来分别放入两个数组里面(sumab[N]和sumcd[N])。然后将sumab[N]从0枚举到N,看是否能在sumcd[N]中找到与其对应的解,这里从sumcd[N]中找到对应的解的过程用到二分。
//头文件略
const int N = 4000+10;
int arr[N][4];
int sumab[N*N],sumcd[N*N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) for(int j=0;j<4;j++) scanf("%d",&arr[i][j]);
int t=0;
for(int i=0;i<n;i++) for(int j=0;j<n;j++) sumab[t++]=arr[i][0]+arr[j][1];
int tt=0;
for(int i=0;i<n;i++) for(int j=0;j<n;j++) sumcd[tt++]=arr[i][2]+arr[j][3];
int sum=0;
sort(sumcd,sumcd+tt);
for(int i=0;i<t;i++)
{
int x=lower_bound(sumcd,sumcd+tt,0-sumab[i])-sumcd;
for(int j=x;j<tt;j++)
{
if(sumcd[j]+sumab[i]>0) break;
if(sumcd[j]+sumab[i]==0) sum++;
}
}
printf("%d\n",sum);
return 0;
}
HDU 2289–Cup
这题是一个圆台,底面半径是r,顶面半径是R,高为H,然后告诉现在装了v升水,问水的高度是多少?
这题也是二分,只不过要确保二分的精度问题。题目要求保留六位小数,那就设 EXP=1e-7;
#define pi acos(-1)
#define EXP 1e-7
using namespace std;
double r,R,H,V;
double cal_v(double x)
{
return pi*x*(r*r+pow(r+(R-r)/H*x,2)+r*(r+(R-r)/H*x))/3.0;
}
double _find(double L,double R)
{
double mid;
while(fabs(L-R)>EXP)
{
mid=(L+R)/2;
if(cal_v(mid)>=V) R=mid;
else L=mid;
}
return L;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%lf %lf %lf %lf",&r,&R,&H,&V);
printf("%.6lf\n",_find(0,H));
}
return 0;
}
(怎么感觉还是掌握不了手写二分…Orz…