第一章 基础算法
快速排序
分治是快排的思想,
调整区间后分界点不一定是x
调整区间方法1
调整区间方法2
双指针,无论什么时候,i指针前面的数都是小于x的,同理j指针后面的数都是大于x的,所以当两个指针相遇穿过,就分成了两个区间,两个指针穿过了就不能交换指针了,
每次交换完两个指针后,都会往中间移动一格,因此可以先不管,直接把两个指针往中间移动一次,然后再进行判定,因此两个指针就需要把两个指针放到边界的左右两侧一格,这样最开始移动一格后,才能到真正的边界,
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int q[N];
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;//判边界,如果里面只有一个数或者没有数就return
int x= q[l],i= l- 1, j=r +1;//分界点x,如果下面quick_sort里写i,此处不能取q[l]因为会发生死循环,无限递归,如果下面下面quick_sort里写j和j+1,此处不能取q[r],会发生无限递归
while (i < j)
{
do i ++ ; while (q[i] < x);
do j-- ; while (q[j] > x);
if(i< j) swap(q[i],q[j]);
//{
//int t = q[i];
//q[i] = q[j];
//q[j] = t;
//}如果没有swap函数自己写
}
quick_sort(q,l,j);//quick_sort(q,l,i-1);注意此处写i了则上面的分界点x就不能取l,因为会发生死循环,如果只有两个数,那么可能会使某个递归一直循环于[0,1]的区间的两个数,同理此处写j和j+1的时候,边界点x就不能取到q[r],否则会发生死循环
quick_sort(q,j+ 1,r);//quick_sort(q,i,r);
}
int main()
{
scanf("%d",&n);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
quick_sort(q,0,n-1);
for (int i = 0; i< n; i ++ ) printf("%d ", q[i]);
return 0;
}
归并排序
归并排序的思想也是分治,但是分治的思想和快排不一样,快排是用一个数来分,分完后左边都比这个数小,右边都比这个数大,归并是以整个数组的中心中间来分,并且是先递归排序左边和右边两边,两边排好序后再合二为一,把两个有序序列合成一个有序序列,在合并的过程中,第一个指针最多只能扫描左边的半边,第二个指针最多只能扫描右边的半边,因此两个指针扫描的总共的长度是O(n),所以合并的时间复杂度是O(n),即每个元素只会被移动一次,快排是先分完再递归两边,
归并排序是稳定的,所以两个相同的数排的时候,要把在前面的那个数先放到序列里,快排是不稳定的,可以把快排里都弄成pair二元组,这样相同的数的下标肯定不同,双关键字排序就肯定能保证所有数都是不同的,这样就可以稳定排序了,
快排的平均时间复杂度是O(nlog2n),最坏时间复杂度是O(n²)但是几乎不可能达到,归并排序的时间复杂度一定是O(nlog2n)
归并排序第一层是n的长度,第二层是n/2的长度,第三层是4个n/4的长度,到最后是n个长度为1的区间,n除以log2n就会变成1,所以总共有logn层,每一层是时间复杂度是O(n)的,第一层是O(n),第二层是两个O(n/2)加一起就是O(n),第三层是4个O(n/4)加一起就是O(n),所以总共就是O(nlogn)
快排也类似,虽然每次划分区间的时候不一定是恰好n/2,但是期望是n/2,所以整个递归的层数也是期望是logn,每层是O(n),一共logn层,所以是O(nlogn)
归并排序需要一个tmp数组来存储最终把两个序列合并成一个序列后的结果,k表示已经合并了几个数了,
#include <iostream>
using namespace std;
const int N = 100001;
int n;
int q[N],tmp[N];
void merge_sort(int q[], int l, int r)//q是要排序的数组,l和r是闭区间的左右边界,
{
if (l >= r) return;//如果当前区间里只有一个数或者没有数,就不用排序了,
int mid = l+ r >> 1;//取中点
merge_sort(q,l,mid), merge_sort(q, mid + 1, r);//递归排序左右边,排完后左右两边就有序了
int k=0,i= l,j= mid +1;
//归并的过程,i是指向左半边区间的起点,j是指向右半边区间的起点,
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l,j= 0; i <= r; i ++, j ++ )
q[i] = tmp[j];
}
int main()
{
scanf("%d",&n);
for (int i = 0;i < n;i++ ) scanf("%d", &q[i]);
merge_sort(q,0,n - 1);
for (int i = 0; i< n; i ++ ) printf("%d ", q[i]);
return 0;
}
整数二分
如果有单调性,就一定可以二分,但是可以二分的题目不一定非要有单调性,即没有单调性也有可能可以二分,二分的本质并不是单调性,二分的本质是区间,如果可以找到这样一个性质,可以把整个区间一分为二,比如右半边满足,左半边不满足(没有交点),二分就可以寻找性质的边界,就可以把边界点二分出来,既可以寻找满足的边界,也可也寻找不满足的边界(下图红绿部分上面的箭头),二分出来绿色的点和红色的点就是两个不同的模板,
//模板
// 区间[1,r]被划分成[l,mid]和[mid + 1,r]时使用:mid属于左边用此模板,此时mid=l+r>>1不用+1
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid =l+r >> 1;
if (check(mid)) r = mid;// check()判断中间值mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l,r]被划分成[1,mid - 1]和[mid,r]时使用:mid属于右边用此模板,此时mid=l+r+1>>1,要有加一
int bsearch_2(int 1, int r)
{
while (l<r)
{
int mid =l+r+1>> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
二分第一步先找中间值mid,每次判断中间值mid是否满足此性质,假设是红色的性质,即check一下是否满足红色的性质,如果check是true,说明mid满足红色的条件,此时mid就在左边红色区间里面,那么答案就应该在【mid,r】,包含mid,此时点是可以取到红色的边界点,因为mid可能是答案,更新方式就是把l,r这个区间更新成【mid,r】区间,所以更新方式就是l=mid,因为r是不变的,如果当mid不满足红色性质的时候,那么mid就一定取在绿色的部分,那么此时答案就在【l,mid-1】里面,因为mid是不满足红色性质的,所以答案边界一定不在mid上,取不到mid,此时更新方式就是把l,r更新成【l,mid-1】区间,l是不变的,把r更新成mid-1就可以,
当想二分绿色的时候,首先求mid=l+r>>1,然后check一下mid是否满足绿色的性质,如果是true说明mid是满足绿色的性质的,那么mid就在绿色这段里,答案就应该在l和mid之间,即答案边界点一定在mid 的左边,并且边界点有可能在mid上,更新方式就是把l,r更新成【l,mid】,反之如果mid不满足绿色的性质,那么mid就会在红色这半边,此时答案就应该在【mid+1,r】之间,不能取到mid,更新方式就是把l,r更新成【mid+1,r】
每次看一下区间是l=mid还是r=mid,l=mid就要补上加一,r=mid就不需要补上加一,因为如果l=r-1,l和r只相差1,如果不补上加一的话,那么mid=l+r>>1向下取整应该等于l,如果此时check是成功的返回true,那么在更新区间的时候,l=mid=l,因此此时更新后l和r还是l和r,没有变化,循环了一遍后l和r没有变,那么下次循环后也不会变,所以就会死循环,因此要补上加一,此时mid =l+r+1>>1就是r,此时更新后l=mid=r,即【l,r】循环一遍后变成【r,r】,那么就会停止,不会发生死循环
c++里的除法是下取整,
具体不用考虑是红色的边界点,还是绿色的边界点,写的时候直接先求mid,然后check判断一下根据check的值想一下答案应该怎么划分,应该是l=mid还是r=mid,如果是第一种,在求mid 的时候补上1就可以了,第二种就不需要补上1,
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int q[N];
//二分的主要思想:在一个区间内部,每次选择答案所在的区间进行下一步的处理,每次把区间的长度缩小一半,选择答案所在的一个区间,每一次都能保证区间里面一定有答案,当区间长度是1的时候,这个区间里面的这个数一定就是答案,二分是一定有解的,比如要找到等于x的这个数,但是数组里面可能不包含x,所以定义性质的时候就要找到从左往右看,第一个满足≥x的数,性质是一定有边界的,
int main()
{
scanf("%d%d",&n, &m);
for (int i = 0; i< n; i++ ) scanf("%d", &q[i]);
while (m -- )
{
int x;
scanf("%d",&x);
int l=0,r=n- 1;
while (l<r)
{
int mid = l +r >> 1;//起始位置不管直接先写mid=l+r,后面再看要不要补加一,然后check条件判断x的起始位置应该是≥x,所以如果满足,那么mid一定在右半边,那么答案即边界的位置一定在左半边,mid也满足这个≥x的性质,mid也有可能取到答案,因此要更新成l到mid,反之一定是l=mid+1。此时是r=mid,所以是第一个模板,就不用加一了,
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
if (q[l] != x) cout <<"-1 -1" << endl;//数组里不存在这个值
else
{
cout << l <<' ';
int l = 0, r = n - 1;//右边界,假设最后一个x,可以定义成小于等于x,≤x,此时左边数包括x都满足性质,如果满足≤x的性质,说明mid在左半边,所以答案是在mid 的右半边,并且mid也满足这个性质,mid也有可能是答案,所以更新区间就要把mid 放到区间里,也就是从mid到r,反之r=mid-1,此时因为是l=mid,所以要补上加一,
while (l <r)
{
int mid =l+r+1>> 1;
if (q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
浮点数二分
因为浮点数边界没有整除,每次区间长度可以严格的缩小一半,所以不需要处理边界,每次都保证答案在区间内部,每次通过中间点判断一下答案落在哪半边,只要每次保证答案落在区间里就可以了,当区间长度很小的时候就可以认为找到了答案,比如r-l≤10的-6次方了,此时就可以用l或r当作自己的答案
求根号x的值,
#include <iostream>
using namespace std;
int main()
{
double x;
cin >> x;
double l = 0,r = x;
while (r - l > 1e-8)//如果题目说保留四位小数,就写e-6,如果保留5位小数,就写e-7,保留6位小数,就写e-8,至少要比要求的有效位数多2,
//还可以不用精度迭代,可以直接循环100次,for (int i = 0;i< 100; i ++ ),相当于把整个区间的长度除以2的100次方,
{
double mid = (l + r) / 2;
if (mid * mid >= x) r = mid;
else l = mid;
}
printf("%lf\n",l);//输出r或者l都可以
return 0;
}
高精度
java有大整数类,没有位数要求,python数自带的默认数的范围是无限大,c++里没有大整数类,当两个很大的整数进行加减乘除的时候,需要高精度,
四种:①两个比较大的整数相加,两个数的位数是10的六次方,②两个比较大的整数相减,③一个大整数乘上一个比较小的整数,大整数位数小于10的六次方,小的数的数值小于10的九次方或者小于10000,⑤一个比较大的数除以一个比较小的数,求商和余数,
大整数用int存不下来,其实是把每一位存到数组里,存的时候数组下标为0的地方存数的个位,下标为1的存十位,
加减乘除里面,大整数的存储都是一致的,
高精度加法
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 10;
// C=A+B
vector<int> add(vector<int> &A,vector<int> &B)//加上引用就不会拷贝整个数组了,效率提高
{
vector<int> C;
int t =0;//最开始进位是0
for (int i = 0;i<A.size()|| i<B.size();i ++ )
{
if (i< A.size()) t += A[i];
if (i< B.size()) t += B[i];
C.push_back(t % 10);//A和B的位加上进位组成C的每一位
t /= 10;
}
if (t) C.push_back(1);//如果最高位有进位,则加上一个1,
return C;
}
int main()
{
string a,b;
vector<int> A,B;
cin >>a>>b;// a="123456"
for (int i = a.size() - 1; i >= 0; i --)A.push_back(a[i]-'0');// A = [6,5,4,3,2,1]
for (int i = b.size() - 1; i >= 0;i-- ) B.push_back(b[i]-'0');
auto C = add(A,B);
for (int i = C.size() - 1; i >= 0; i-- ) printf("%d",C[i]);
return 0;
}
高精度减法
大整数减法:第一步判断如果A>B,直接算A-B, 如果A<B,就计算-(B-A),每次运算都是较大的数减去较小的数,一定不会出现负数的情况,因此最高位一定不会再往前借位了,可以少处理一些边界情况,
#include <iostream>
#include <vector>
using namespace std;
// 判断是否有 A>= B
bool cmp(vector<int> &A,vector<int> &B)
{
if (A.size() != B.size()) return A.size()> B.size();
for (int i = A.size() - 1;i >= 0; i --)
if (A[i] != B[i])
return A[i] > B[i];
return true;//如果两个数每一位都相等,返回true,因为等于也属于A>=B
}
//如果t≥0,就是t本身,如果t<0,就是t+10 ,所以用(t+10)%10把这两种情况合二为一
//C=A-B
vector<int> sub(vector<int> &A,vector<int> &B)
{
vector<int> C;
for (int i = 0,t =0; i< A.size(); i ++ )
{
t =A[i]- t;
if (i< B.size()) t -= B[i];//判断一下B的位数是否存在,因为B的位数可能比A的少,
C.push_back((t + 10) % 10);
if (t<0) t=1;//t=0表示没有借位,t=1表示借位了,
else t = 0;
}
while (C.size() > 1 && C.back()== 0) C.pop_back();//去掉前导0
return C;
}
int main()
{
string a, b;
vector<int> A,B;
cin >>a>>b;//a="123456"
for (int i = a.size() - 1; i >= 0; i --)A.push_back(a[i]-'0');// A = [6,5,4,3,2,1]
for (int i = b.size() - 1; i >= 0;i-- ) B.push_back(b[i]-'0');
if (cmp(A,B))
{
auto C = sub(A,B);
for (int i= C.size() - 1; i >= 0; i -- ) printf("%d",C[i]);
}
else
{
auto C = sub(B,A);
printf("-");
for (int i = C.size() - 1; i >= 0; i -- ) printf("%d",C[i]);
}
return 0;
}
高精度乘法
#include <iostream>
#include <vector>
using namespace std;
vector<int> mul(vector<int> &A,int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i<A.size() || t; i++ )//要不是i没有循环完,要不是t不为0
{
if (i< A.size()) t += A[i]* b;
C.push_back(t % 10);
t /= 10;
}
return C;
}
int main()
{
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i-- ) A.push_back(a[i] -'0');
auto C = mul(A, b);
for (int i= C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
return 0;
}
高精度的整数除以低精度的整数,
高精度除法
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
//A/b 商是C,余数是r,
//加法减法乘法都是从第一位开始算,而除法是从最高位开始算,
vector<int> div(vector<int> &A,int b,int &r)//余数是通过引用传回去的
{
vector<int> C;
r=0;
for (int i = A.size() - 1; i >= 0;i-- )
{
r=r*10+A[i];
C.push_back(r / b);
r%= b;
}
reverse(C.begin(),C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();//最高位是0,去掉前导0
return C;
}
int main()
{
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i-- ) A.push_back(a[i] -'0');
int r;
auto C = div(A, b,r);
for (int i= C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);
cout <<endl;
cout <<r<<endl;
return 0;
}
前缀和
下标从1开始,S0=0,更好处理边界问题,可以用相减的方式统一的处理,
前r个数的和减去前l-1个数的和就是l到r个数的和,
ios::sync_with_stdio(false); //让cin和标准输入输出不同步,作用是提高cin的读取速度,副作用是不能再使用scanf了,
#include <iostream>
using namespace std;
const int N = 10010;
int n, m;
int a[N], s[N];
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];//前缀和的初始化
while (m -- )
{
int l, r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r] - s[l - 1]);//区间和的计算
}
return 0;
}
二维前缀和
#include <iostream>
const int N = 1010;
int n, m, q;
int a[N][N],s[N][N];
int main()
{
scanf("%d%d%d",&n, &m, &q);
for (int i= 1;i <= n;i ++ )
for (int j= 1;j <= m; j ++ )
scanf("%d",&a[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j= 1; j <= m; j ++ )
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];//求前缀和
while (q -- )
{
int x1, y1, x2, y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);//算子矩阵部分和
}
return 0;
}
差分是前缀和的逆运算
a数组是b数组的前缀和,那么b数组就称为a数组的差分,
想让a数组里某个区间里的数加上c的话,只需修改b数组里的两个数就可以,可以用O(1)复杂度给原数组某一个区间的数全部加上一个固定的值,bl加上c使得a数组al以及al之后的所有的a数组的元素都会加上c,而因为r之后的a不需要加上c,所以br+1减去c使得a数组的r+1之后的元素都减去一个c,注意此处a是b的前缀和
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N],b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r+1]-= c;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) insert(i,i, a[i]);//a数组的每个值都是插入进去的,这样与计算后面的b【i】统一
while(m-- )
{
int l, r, c;
scanf("%d%d%d",&l,&r, &c);
insert(l,r,c);
}
for (int i = 1; i <= n; i++ ) b[i] += b[i - 1];
for (int i = 1; i <= n; i ++ ) printf("%d ",b[i]);
return 0;
}
二维差分
二维差分是给其中的子矩阵加上一个固定的值,
ai,j是差分矩阵bi,j的前缀和,差分不用考虑构造,假定刚开始ai,j都是0,将a数组每个元素插入就可以了
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N],b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1]-=c;
b[x1][y2 + 1]-=c;
b[x2 + 1][y2 + 1] +=c;
}
int main()
{
scanf("%d%d%d",&n,&m, &q);
for (int i = 1; i <= n; i ++ )
for (int j= 1; j <= m; j ++ )
scanf("%d",&a[i][j]);
for (int i = 1;i<= n; i ++ )
for (int j= 1; j<= m; j ++ )
insert(i,j,i,j,a[i][j]);
while (q -- )
{
int x1,y1, x2, y2,c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1,y1,x2, y2, c);
}
for (int i = 1; i <= n; i ++ )
for (int j= 1;j<= m; j ++ )
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];//矩阵求前缀和,上一题也有前缀和矩阵的求法,对比看
for (int i = 1; i <= n; i ++ )
{
for (int j= 1; j<= m; j ++ )printf("%d ",b[i][j]);
puts("");
}
return 0;
}
双指针算法
第一类:在两个序列里面,一个指针指向其中一个序列,另外一个指针指向另外一个序列,
第二类:指向一个序列,
双指针就可以把暴力做法O(n²)优化到O(n)
每个单词输出占一行
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char str[1000];
fgets(str,100,stdin);
int n = strlen(str);
for (int i = 0; i< n; i ++ )
{
int j = i;
while (j< n && str[j] !=' ') j++ ;
// 这道题的具体逻辑
for (int k = i; k < j; k ++ ) cout << str[k];
cout << endl;
i=j;
}
return 0;
}
红色是i,绿色是j,j是单调往后走的,
a是原来的数组,s数组存的是当前j到i区间内每一个数出现的次数,
#include <iostream>
using namespace std;
const int N= 100010;
int n;
int a[N],s[N];
int main()
{
cin >> n;
for (int i= 0;i<n;i++ ) cin >> a[i];
int res = 0;
for (int i= 0,j=0;i<n; i++ )
{
s[a[i]] ++ ;
while (s[a[i]] > 1)
{
s[a[j]]--;
j++ ;
}
res = max(res,i - j + 1);
}
cout << res << endl;
return 0;
}
位运算
#include <iostream>#include <string.h>
using namespace std;
int main()
{
int n = 10;
for (int k = 3; k >= 0; k -- ) cout << (n >> k & 1) ;
return 0;
}
lowbit可以统计一下x里面1的个数,
#include <iostream>
using namespace std;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
int res = 0;
while (x)x -= lowbit(x),res ++ ;// 每次减去x的最后一位1
cout << res << ' ';
}
return 0;
}
整数的保序的离散化
保序:小的一定在前面,大的一定在后面,
unique是把数组中所有的元素进行去重,并且返回去重之后的数组的尾端点,剩余重复的元素会放到最后的部分用erase删掉就可以了,
//离散化
vector<int> alls;// 存储所有待离散化的值
sort(alls.begin(),alls.end());// 将所有值排序
alls.erase(unique(alls.begin(),alls.end()),alls.end();// 去掉重复元素
//二分求出x对应的离散化的值
int find(int x)// 从左往右找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
映射成的值就是数组的下标,
把里面用到过的不同的数映射成从1开始的自然数,
读入的区间的两个端点也需要离散化,最开始插入的c值的下标也需要离散化,find把x映射成从1开始的自然数,
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 300010;
int n, m;
int a[N],s[N];
vector<int> alls;
vector<PII> add,query;
//二分求出x对应的离散化的值
int find(int x)// 从左往右找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
int main()
{
cin >> n >> m;
for (int i= 0;i<n;i ++ )
{
int x, c;
cin >>x >>c;
add.push_back({x,c});
alls.push_back(x);
}
for (int i=0;i<m; i ++ )
{
int l, r;
cin >> l >>r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
//去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(), alls.end()),alls.end());
//处理插入
for (auto item : add)
{
int x = find(item.first);
a[x] += item.second;
}
// 预处理前缀和
for (int i = 1; i<= alls.size(); i ++ ) s[i] = s[i - 1] + a[i];
// 处理询问
for (auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
//unique函数
vector<int>::iterator unique(vector<int> &a)
{
int j = 0;
for (int i= 0;i<a.size(); i ++)
if(!i || a[i] != a[i - 1])
a[j++]= a[i];
// a[0]~a[j- 1] 所有a中不重复的数
return a.begin() + j;
}
区间合并
把有交集的区间合并成一个区间,
pair在c++里排序会优先以左端点排序,再以右端点排序,
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 10001;
int n;
vector<PII> segs;
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(),segs.end());
int st = -2e9,ed = -2e9;
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed});//把左边的区间放进res,不是把segs里的当前的seg放入res,表示没有交集
st = seg.first,ed = seg.second;//更新st 和seg
}
else ed = max(ed, seg.second);//否则区间有交集
if (st != -2e9) res.push_back({st,ed});
segs = res;
}
int main()
{
cin >> n;
for (int i = 0;i<n;i ++ )
{
int l, r;
cin >> l >> r;
segs.push_back({l,r});
}
merge(segs);
cout << segs.size() << endl;
return 0;
}
习题
#include <iostream>
using namespace std;
const int N = 100010;
int n, k;int q[N];
int quick_sort(int l, int r, int k)
{
if (l == r) return q[l];//此处可以写≥,因为和二分一样,时刻保证区间里最少有一个数,但是快排必须写l≥r,因为快排里面,区间里面可能是没有数的,有可能出现l>r的情况,
int x = q[l],i= l- 1, j=r + 1;
while (i < j)
{
while (q[ ++ i]< x);
while (q[-- j] >x);
if (i< j) swap(q[i],q[j]);
}
int sl=j-l+1;//左半边区间是l到j,右半边区间是j+1到r,左半边从l到j一共j-r+1个数
if (k <= sl) return quick_sort(l, j, k);
return quick_sort(j + 1, r, k - sl);
}
int main()
{
cin >> n >> k;
for (int i = 0; i< n; i ++ ) cin >> q[i];
cout << quick_sort(0, n - 1, k) << endl;
return 0;
}
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;int q[N], tmp[N];
LL merge_sort(int l, int r)
{
if (l >= r) return 0;
int mid = l + r >> 1;
LL res = merge_sort(l, mid) + merge_sort(mid + 1, r);
// 归并的过程
int k = 0,i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else
{
tmp[k ++ ] = q[j ++ ];
res += mid - i + 1;//i后面的数都比j大,都能构成逆序
}
// 扫尾的时候一定有一边已经扫完了,此时没有数对了,所以不需要加了,下面两个循环最多只会执行一个循环,
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
// 物归原主
for (int i= l,j= 0; i <= r; i++, j ++ ) q[i] = tmp[j];
return res;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i];
cout << merge_sort(0, n - 1) << endl;
return 0;
}
浮点数的二分
printf是默认保留6位小数的,
#include <iostream>
using namespace std;
int main()
{
double x;
cin >> x;
double l = -10000,r = 10000;
while (r - l> 1e-8)
{
double mid = (l + r) / 2;
if (mid * mid *mid >= x) r = mid;
else l = mid;
}
printf("%lf\n",l);
return 0;
}
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N],s[N];
int main()
{
cin >> n >> m;
for (int i= 1;i <= n; i ++ ) cin >> a[i];
// 求前缀和数组
for (int i = 1;i <= n; i++ ) s[i] = s[i - 1] + a[i];
while (m -- )
{
int l, r;
cin >> l>> r;
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
S[i,j]的含义就是每一个【i,j】这个格子左上角所有数的和
有减一的操作最好从下标从1开始,
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N],s[N][N];
int main()
{
scanf("%d%d%d",&n,&m,&q);
for (int i=1;i <= n;i ++ )
for (int j= 1;j<= m; j ++)
scanf("%d",&a[i][j]);
// 初始化前缀和数组
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
// 询问
while (q -- )
{
int x1, y1,x2, y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
}
return 0;
}
差分:前缀和的逆运算
本来需要循环一遍,把O(n)时间复杂度变成O(1)
初始化的时候假定a数组都是0,此时构造的差分数组也全都是0,可以看成在全是0的数组上进行了n步的插入操作,
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r +1] -= c;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) insert(i, i, a[i]);
while (m -- )
{
int l, r, c;
cin >> l >> r >> c;
insert(l,r,c);
}
for (int i= 1; i <= n; i ++ )a[i]= a[i - 1] + b[i];
for (int i = 1; i <= n; i ++ ) printf("%d ",a[i]);
puts("");
return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N],b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2 + 1]-=c;
b[x2 + 1][y1]-=c;
b[x2 + 1][y2 + 1] +=c;
}
int main()
{
scanf("%d%d%d",&n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d",&a[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j= 1;j <= m; j++ )
insert(i,j,i,j,a[i][j]);
while (q--)
{
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d",&x1,&y1, &x2,&y2,&c);
insert(x1,y1,x2,y2,c);
}
for (int i = 1; i <= n; i ++ )
for(int j=1;j<=m;j++ )
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
for (int i = 1; i <= n; i ++ )
{
for (int j= 1; j <= m; j ++ ) printf("%d ",a[i][j]);
puts("");
}
return 0;
}