1.归并
归并,分治。两者概率模糊。有父子的关系。
归并用递归去实现
‘分’是为了方便‘治’,‘治’有助于‘归’
void fun(...){
终止条件;
分;
治;
归;
}
归并排序
int a[maxn];//待排序数组
int p[maxn];//拷贝数组
void fun(int f,int l){ //函数参数用数组也可以 ,没必要,数组当参数无所谓的 不用考虑爆栈、传过来的是一个地址指针
if(f==l) return ;//终
int mid=f+l>>1;//分
fun(f,mid),fun(mid+1,l);
int i=f,j=mid+1,k=0;//治
while(i<=mid&&j<=l){
if(a[i]<a[j]){
p[k++]=a[i++];
}else{
p[k++]=a[j++];
}
}
while(i<=mid){p[k++]=a[i++];}
while(j<=l){p[k++]=a[j++];}
for(i=0,j=f;i<k;i++,j++){//归
a[j]=p[i];
}
}
二分
二分查询的东西首先要满足某种规律。多见于对于答案二分。
二分困难的点在于对边界的判断,cheack函数时 <= >=
二分的思路其实像一种数字游戏:
A在纸上写了一个1到100之内任意的一个数,B去猜
B猜50,A回答大了
B猜25,A回答小了
B猜34,。。。。。
至于模板有三种。。。。前两种来自闫总模板,后面一种是李秉庚的模板。
//[f,mid][mid+1,l]
int fun(int f,int l){
int mid;
while(f<l){
mid=f+l>>1;
if(check(mid)){
f=mid+1;
}else{
l=mid;
}
}
return f;
}
//[f,mid-1][mid,l]
int fun(int f,int l){
int mid;
while(f<l){
mid=f+l>>1;
if(check(mid)){
f=mid;
}else{
l=mid-1;
}
}
return f;
}
int fun(int f,int l){
int mid;
while(f<=l){
mid=f+l>>1;
if(check(mid)){
f=mid+1;
}else{
l=mid-1;
}
}
return l;
}
高精度
这个不是算法,是最基本的代码实现能力,不说。
前缀和差分
对于前缀和
结合高中前缀和的知识。
对于一个数组
a1,a2,a3,a4,a5。。。。
令
s1=a1
s2=a1+a2
s3=a1+a2+a3
s4=a1+a2+a3+a4
s5=a1+a2+a3+a4+a5
不难推出 a4=s4-s3;
在程序里面也存在这样的事实。
前缀和是一种思想,他没有牺牲空间,直接让查询时间变成O(1)
对于差分
差分和前缀和相对。
对于上面的例子 S是A的前缀和数组,那么A就是S的差分数组。
差分使用的地方。。。对前缀和数组倒过来想、是在对一段数组进行操作。
至于代码,不至于给出了
双指针算法
双指针算法的理解一直是很模糊的概率,双指针?像归并中的i,j。
但其实这一类算法,不是算法,还是思想。
写出最朴素的算法,优化朴素
位运算
二进制是计算机最朴素的公民。至少目前位置。01加上定义的规则,01就是整个世界。
状态压缩
0和1可以表达逻辑真假。bool就是这么干的。但是bool浪费了3个位的0!!!!
那么一个longlong可以记录64个人的状态。
快速幂
这里不太想把快速幂和状态压缩分开讲,位运算版的快速冥其实就是记状态。
假设一个2^7次方可以写成
2 ^ 4 * 2 ^ 2 * 2 like (4+2+1)
二进制可以表达任意的数字。所以能用次方的位来判断ans结果需不需要乘上当前数。
当前数即上一个数的平方 不要问我初始化。最初状态、边界 永远是一件神器的事情。也许就像刚开始学背包问题。
int fun(int a,int b){//a^b
int ans=1;
while(b){
if(b&1) ans*=a;
a * = a;
b >> =1;
}
return ans;
}
离散化、区间合并
把不连续的东西连续起来 ,比如一个数组t[]
t[1]和t[11111]存了东西 ,但是1到11111之间都没有元素,这中间的查询时白浪费的 ,如果是一些 不想桶排那样的 可以直接压缩数组t
让t[1]之后链接的元素就是t[11111]