一、二分
1.整数集合上的二分
1)在单调增序列a中查找>=x的数最小的一个:
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
return a[l];
2)在单调增序列a中查找<=x的数中最大的一个
while(l<r)
{
int mid=(l+r+1)/2;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
return a[l];
根据不同的问题合理选择r=mid,l=mid+1,mid=(l+r)/2或者l=mid,r=mid-1,mid=(l+r+1)/2中的一种
2.实数域上的二分:
需要确定好精度eps,以l+eps<r为循环条件:
while(l+eps<r)
{
double mid=(l+r)/2;
if(calc(mid)) r=mid;
else l=mid;
}
也可以用循环固定次数的二分法:(精度往往比比上面的高)
for(int i=0;i<100;i++)
{
double mid=(l+r)/2;
if(calc(mid)) r=mid;
else l=mid;
}
3.三分求单峰函数极值:
函数极值左边严格递增(或递减),极值右边严格递减(或递增)
double lmid, rmid;
while ( low + eps < high )
{
lmid = (low + high) / 2;
rmid = (lmid + high ) / 2;
double clmid = cal(lmid);
double crmid = cal(rmid);
if ( clmid > crmid )
high = rmid;
else
low = lmid;
}
二、排序
1.排序种类:
1)选择排序、插入排序、冒泡排序
2)堆排序、归并排序(可以用于求解逆序对)、快速排序
3)计数排序、基数排序、桶排序(这三种时间复杂度最低)
2.离散化:
把无穷多个元素映射为有限的集合的方法。
当问题有n个数,但是求解只涉及m个数,可以将这m个数与1~m建立一一映射关系。可以将这m个数去重,存到b数组中,建立b[i]与i的关系,只需要在数组中b查找所需要的数即可。
void discrete()
{
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
if(i==1||a[i]!=a[i-1])
b[++m]=a[i];
}
void query(int x)
{
return lower_bound(b+1,b+m+1,x)-b;
}
3.中位数:
对于许多问题求最优解就是求解中位数。
4.第k大数:
实际上运用了快速排序的思想。
选取一个数x做基准,<x放在左边,>x放在右边。然后判断x的位置与k的关系,若>k则在左边继续操作,若<k在右边继续操作,依此类推,时间复杂度比快速排序小,为O(n)。
5.逆序对
运用了归并排序的思想,将所有的数一步步二分为一个个的小区间,从小区间到大区间判断逆序对的个数,返回上一级的区间。
void merge(int l,int mid,int r)
{
//合并a[l~mid]与a[mid+1~r]
//a是待排序数组,b是临时数组,cnt是逆序对个数
int i=l,j=mid+1;
for(int k=l;k<=r;k++)
if(j>r||i<=mid&&a[i]<a[j])
b[k]=a[i++];
for(int k=l;k<=r;k++)
a[k]=b[k];
}