Bootstrap

第一讲 基础算法

#快速排序

#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 ;
int x = q[l],i = l-1,j = r+1;
while(i<j){
	do 
	i++ ;
	while(q[i]<x);
	do 
	j--;
	while(q[j] > x);
	if(i<j)swap(q[i],q[j]);
	
	}
	quick_sort(q,l,j);
	quick_sort(q,j+1,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;
}

解释:

  • 你的代码使用 do-while 结构,首先调整 ij,然后进行比较。这导致 ij 会分开超过数组边界(即允许 i 超过 rj 低于 l)。
  • 普通实现通常在调整 ij 位置后,才进行交换。这种方法确保指针最终会停在需要交换的元素处。

3. 递归调用的差异

你的代码:
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
普通实现代码:
quick_sort(arr, low, i - 1);
quick_sort(arr, i, high);

解释:

  • 你的代码使用 j 作为分界点,并在递归调用中包括 jj+1
  • 普通实现使用 i 作为新的分界点,这与上述 while 循环的条件设计更为一致,因为 i 最终会停在正确位置上。

深入分析

初始化 and do-while VS. while
  • 你的 do-while 结构确保在第一次迭代时,指针 ij 会被推进,而没有停留在起始位置。典型的 while 实现直接在初始位置进行比较,可能产生不同的迭代次数。
  • 你的码使得指针在每次 do-while 循环后都会移动,确保每次都进行调整。这种方法更关注于实际数据布局,在边界条件上可能不同。
递归调用的区域分割
  • 你的代码用 j 作为边界分割点,这使得分割后的两部分各包含了 jj+1 的索引。因此需要通过分别处理的部分来保证所有元素被正确排序。
  • 普通实现更常见的在 ii-1 来分割区域,其中交换过的 i 自然划分出两部分,在处理边界时稍微简化。

改进建议

两种写法的关键差异在于索引如何初始化、条件如何处理,以及递归如何分割区域。在大多数情况下,标准写法通常是这样:

void quick_sort(int arr[], int low, int high) {
    if (low < high) {
        int pivot = partition(arr, low, high); // partition() 分割的 implement 随实现细节决定
        quick_sort(arr, low, pivot - 1);
        quick_sort(arr, pivot, high);
    }
}

总结

  • 初始化索引:你使用宽松的索引初始边界 (l-1r+1),逻辑上确保至少一次扫描。标准方法使用实际边界。
  • 循环结构和条件:你的 do-while 保证在调整后比较有助于不同分界策略。标准 while 内部控制较为紧密。
  • 递归调用:使用 j 作为分界符,符合分区扫描,而普通实现侧重 i

尽管实现细节有差异,如果都严格调试实现,最终排序结果是一致的,但执行细节和性能可能有所不同。

快速排序算法模版
功能:快速排序  
输入:数组名称(也就是数组首地址)、数组中起止元素的下标  
================================================  
*/  
void quick_sort(int *x, int low, int high)  
{  
int i, j, t;  
if (low < high) /*要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为  
low 的元素为基准点*/  
{  
i = low;  
j = high;  
t = *(x+low); /*暂存基准点的数*/  
while (i<j) /*循环扫描*/  
{  
while (i<j && *(x+j)>t) /*在右边的只要比基准点大仍放在右边*/  
{  
j--; /*前移一个位置*/  
if(i<j)
*(x+j) = *(x+i);
i++;
}  
while (i<j && *(x+i)<=t) /*在左边的只要小于等于基准点仍放在左边*/  
{  
i++; /*后移一个位置*/  
}  
if (i<j)  
{  
*(x+j) = *(x+i); /*上面的循环退出:*/  
j--;/*前移一个位置*/  
}  
}  
*(x+i) = t; /*一遍扫描完后,放到适当位置*/  
quick_sort(x,low,i-1); /*对基准点左边的数再执行快速排序*/  
quick_sort(x,i+1,high); /*对基准点右边的数再执行快速排序*/  
}  
}  
/*

#include <iostream>
using namespace std;
const int N = 1e6;
int q[N];
void quick_sort(int q[],int r ,int l)
{
	if(l>=r)
	return ;
	int i = l , j = r ;
	int t = q[l];
	while(i<j)
	{
	while(t >q[i])
	i++;
	while(t<q[j])
	j--;
	if(i<j)
{
	swap(q[i],q[j]);
	i++;
	j--;
}	
	}
	quick_sort(q,l,i-1);
	quick_sort(q,i+1,r);
	
}
int main(){
quick_sort(q,0,N-1);
return 0;
}

#归并排序

#include <iostream>
using namespace std;
const int N = 1000010;
int n;
int q[N],tmp[N];
void merge_sort(int q[],int l ,int 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;
	  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;
}

#二分法

模版
//区间[l+r]被划分为[1,mid]和[mid+1,r]时使用
int bsearch_1(int l ,int r){ 
	while(l<r){
	int mid = 1+r>>1;
	if(check(mid)) r = mid;
	else l = mid + 1;
	
	}
	return 1;
}
//区间[l,r]被划分为[l,mid - 1]和[mid , r]时使用
int bsearch_2(int l,int r){
	while(l < r)
	{
		int mid = l+r+1 >> 1;
		if(check(mid)) l = mid;
		else r = mid -1;
	}
	return 1;
}
查找数字
#include <iostream>
using namespace std;
const int N = 1000010;
int n,m;
int q[N];
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;
		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;
		while(l<r){
			int mid = l+r+1 >>1;
			if(q[mid] <= x)l = mid;
			else r = mid -1;
		
		}
	cout << l << endl;
	
	}
	
}
return 0;
}
开平方
#include <iostream>
using namespace std;
int main (){
	 double x;
	 cin >> x;
	 double l =0,r = x;
	 while(r-l>1e-8){
		 double mid = l+r >>1;
		 if(mid* mid >= x)r = mid;
		 else l = mid;
	 }
	 printf("%lf\n",l);
	 return 0;
}//由于为整数找精确度最接近的数,所以不能分三种情况讨论,该写法,可以尽可能的找到mid,并且返回值,不能对其mid进行修改

排序内容
  
for (i=1; i<n; i++) /*要选择的次数:1~n-1 共 n-1 次*/  
{  
/*  
暂存下标为 i 的数。注意:下标从 1 开始,原因就是开始时  
第一个数即下标为 0 的数,前面没有任何数,单单一个,认为  
它是排好顺序的。  
*/  
t=*(x+i);  
for (j=i-1; j>=0 && t<*(x+j); j--) /*注意:j=i-1,j--,这里就是下标为 i 的数,在它前面有序列中  
找插入位置。*/  
{  
*(x+j+1) = *(x+j); /*如果满足条件就往后挪。最坏的情况就是 t 比下标为 0 的数都小,  
它要放在最前面,j==-1,退出循环*/  
}  
*(x+j+1) = t; /*找到下标为 i 的数的放置位置*/  
}  
}  
/*  
================================================  
功能:冒泡排序  
输入:数组名称(也就是数组首地址)、数组中元素个数  
================================================  
*/  
void bubble_sort(int *x, int n)  
{  
int j, k, h, t;  
for (h=n-1; h>0; h=k) /*循环到没有比较范围*/  
{  
for (j=0, k=0; j<h; j++) /*每次预置 k=0,循环扫描后更新 k*/  
{  
if (*(x+j) > *(x+j+1)) /*大的放在后面,小的放到前面*/  
{  
t = *(x+j);  
*(x+j) = *(x+j+1);  
*(x+j+1) = t; /*完成交换*/  
k = j; /*保存最后下沉的位置。这样 k 后面的都是排序排好了的。*/  
}  
}  
}  
}  
/*  
================================================  
功能:希尔排序

输入:数组名称(也就是数组首地址)、数组中元素个数  
================================================  
*/  
void shell_sort(int *x, int n)  
{  
int h, j, k, t;  
for (h=n/2; h>0; h=h/2) /*控制增量*/  
{  
for (j=h; j<n; j++) /*这个实际上就是上面的直接插入排序*/  
{  
t = *(x+j);  
for (k=j-h; (k>=0 && t<*(x+k)); k-=h)  
{  
*(x+k+h) = *(x+k);  
}  
*(x+k+h) = t;  
}  
}  
}  
/*  
================================================  
功能:快速排序  
输入:数组名称(也就是数组首地址)、数组中起止元素的下标  
================================================  
*/  
void quick_sort(int *x, int low, int high)  
{  
int i, j, t;  
if (low < high) /*要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为  
low 的元素为基准点*/  
{  
i = low;  
j = high;  
t = *(x+low); /*暂存基准点的数*/  
while (i<j) /*循环扫描*/  
{  
while (i<j && *(x+j)>t) /*在右边的只要比基准点大仍放在右边*/  
{  
j--; /*前移一个位置*/  
}  
if (i<j)  
{

*(x+i) = *(x+j); /*上面的循环退出:即出现比基准点小的数,替换基准点的数*/  
i++; /*后移一个位置,并以此为基准点*/  
}  
while (i<j && *(x+i)<=t) /*在左边的只要小于等于基准点仍放在左边*/  
{  
i++; /*后移一个位置*/  
}  
if (i<j)  
{  
*(x+j) = *(x+i); /*上面的循环退出:即出现比基准点大的数,放到右边*/  
j--; /*前移一个位置*/  
}  
}  
*(x+i) = t; /*一遍扫描完后,放到适当位置*/  
quick_sort(x,low,i-1); /*对基准点左边的数再执行快速排序*/  
quick_sort(x,i+1,high); /*对基准点右边的数再执行快速排序*/  
}  
}  
/*  
================================================  
功能:堆排序  
输入:数组名称(也就是数组首地址)、数组中元素个数  
================================================  
*/  
/*  
功能:渗透建堆  
输入:数组名称(也就是数组首地址)、参与建堆元素的个数、从第几个元素开始  
*/  
void sift(int *x, int n, int s)  
{  
int t, k, j;  
t = *(x+s); /*暂存开始元素*/  
k = s; /*开始元素下标*/  
j = 2*k + 1; /*右子树元素下标*/  
while (j<n)  
{  
if (j<n-1 && *(x+j) < *(x+j+1))/*判断是否满足堆的条件:满足就继续下一轮比较,否则调整。  
*/  
{  
j++;  
}  
if (t<*(x+j)) /*调整*/  
{  
*(x+k) = *(x+j);

k = j; /*调整后,开始元素也随之调整*/  
j = 2*k + 1;  
}  
else /*没有需要调整了,已经是个堆了,退出循环。*/  
{  
break;  
}  
}  
*(x+k) = t; /*开始元素放到它正确位置*/  
}  
/*  
功能:堆排序  
输入:数组名称(也就是数组首地址)、数组中元素个数  
*/  
void heap_sort(int *x, int n)  
{  
int i, k, t;  
//int *p;  
for (i=n/2-1; i>=0; i--)  
{  
sift(x,n,i); /*初始建堆*/  
}  
for (k=n-1; k>=1; k--)  
{  
t = *(x+0); /*堆顶放到最后*/  
*(x+0) = *(x+k);  
*(x+k) = t;  
sift(x,k,0); /*剩下的数再建堆*/  
}  
}  
/*构造随机输出函数类*/  
void input(int a[]){  
int i;  
srand( (unsigned int)time(NULL) );  
for (i = 0; i < 4; i++)  
{  
a[i] = rand() % 100;  
}  
printf("\n");  
}  
/*构造键盘输入函数类*/  
/*void input(int *p)  
{  
int i;

printf("请输入 %d 个数据 :\n",MAX);  
for (i=0; i<MAX; i++)  
{  
scanf("%d",p++);  
}  
printf("\n");  
}*/  
/*构造输出函数类*/  
void output(int *p)  
{  
int i;  
for ( i=0; i<MAX; i++)  
{  
printf("%d ",*p++);  
}  
}  
// 归并排序中的合并算法  
void Merge(int a[], int start, int mid, int end)  
{  
int i,k,j, temp1[10], temp2[10];  
int n1, n2;  
n1 = mid - start + 1;  
n2 = end - mid;  
// 拷贝前半部分数组  
for ( i = 0; i < n1; i++)  
{  
temp1[i] = a[start + i];  
}  
// 拷贝后半部分数组  
for (i = 0; i < n2; i++)  
{  
temp2[i] = a[mid + i + 1];  
}  
// 把后面的元素设置的很大  
temp1[n1] = temp2[n2] = 1000;  
// 逐个扫描两部分数组然后放到相应的位置去  
for ( k = start, i = 0, j = 0; k <= end; k++)  
{  
if (temp1[i] <= temp2[j])  
{  
a[k] = temp1[i];

i++;  
}  
else  
{  
a[k] = temp2[j];  
j++;  
}  
}  
}  
// 归并排序  
void MergeSort(int a[], int start, int end)  
{  
if (start < end)  
{  
int i;  
i = (end + start) / 2;  
// 对前半部分进行排序  
MergeSort(a, start, i);  
// 对后半部分进行排序  
MergeSort(a, i + 1, end);  
// 合并前后两部分  
Merge(a, start, i, end);  
}  
}  
/*顺序查找*/  
void SequenceSearch(int *fp,int Length)  
{  
int i;  
int data;  
printf("开始使用顺序查询.\n 请输入你想要查找的数据.\n");  
scanf("%d",&data);  
for(i=0;i<Length;i++)  
if(fp[i]==data)  
{  
printf("经过%d 次查找,查找到数据%d,表中位置为%d.\n",i+1,data,i);  
return ;  
}  
printf("经过%d 次查找,未能查找到数据%d.\n",i,data);  
}  
/*二分查找*/  
void Search(int *fp,int Length)  
{  
int data;

int bottom,top,middle;  
int i=0;  
printf("开始使用二分查询.\n 请输入你想要查找的数据.\n");  
scanf("%d",&data);  
printf("由于二分查找法要求数据是有序的,现在开始为数组排序.\n");  
Sort(fp,Length);  
printf("数组现在已经是从小到大排列,下面将开始查找.\n");  
bottom=0;  
top=Length;  
while (bottom<=top)  
{  
middle=(bottom+top)/2;  
i++;  
if(fp[middle]<data)  
{  
bottom=middle+1;  
}  
else if(fp[middle]>data)  
{  
top=middle-1;  
}  
else  
{  
printf("经过%d 次查找,查找到数据%d,在排序后的表中的位置为%d.\n",i,data,middle);  
return;  
}  
}  
printf("经过%d 次查找,未能查找到数据%d.\n",i,data);  
}  
void Sort(int *fp,int Length)  
{  
int temp;  
int i,j,k;  
printf("现在开始为数组排序,排列结果将是从小到大.\n");  
for(i=0;i<Length;i++)  
for(j=0;j<Length-i-1;j++)  
if(fp[j]>fp[j+1])  
{  
temp=fp[j];  
fp[j]=fp[j+1];  
fp[j+1]=temp;  
}  
printf("排序完成!\n 下面输出排序后的数组:\n");

for(k=0;k<Length;k++)  
{  
printf("%5d",fp[k]);  
}  
printf("\n");  
}  
void main()  
{  
int start=0,end=3;  
int *p, i, a[MAX];  
int count=MAX;  
int arr[MAX];  
int choise=0;  
/*printf("请输入你的数据的个数:\n");  
scanf("%d",&count);*/  
/* printf("请输入%d 个数据\n",count);  
for(i=0;i<count;i++)  
{  
scanf("%d",&arr[i]);  
}*/  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
do  
{  
printf("1.使用顺序查询.\n2.使用二分查找法查找.\n3.退出\n");  
scanf("%d",&choise);  
if(choise==1)  
SequenceSearch(a,count);  
else if(choise==2)  
Search(a,count);  
else if(choise==3)  
break;  
} while (choise==1||choise==2||choise==3);  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");

/*测试选择排序*/  
p = a;  
printf("选择排序之后的数据:\n");  
select_sort(p,MAX);  
output(a);  
printf("\n");  
system("pause");  
/**/  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
/*测试直接插入排序*/  
printf("直接插入排序之后的数据:\n");  
p = a;  
insert_sort(p,MAX);  
output(a);  
printf("\n");  
system("pause");  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
/*测试冒泡排序*/  
printf("冒泡排序之后的数据:\n");  
p = a;  
insert_sort(p,MAX);  
output(a);  
printf("\n");  
system("pause");  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
/*测试快速排序*/  
printf("快速排序之后的数据:\n");  
p = a;  
quick_sort(p,0,MAX-1);  
output(a);  
printf("\n");  
system("pause");
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
/*测试堆排序*/  
printf("堆排序之后的数据:\n");  
p = a;  
heap_sort(p,MAX);  
output(a);  
printf("\n");  
system("pause");  
/*录入测试数据*/  
input(a);  
printf("随机初始数组为:\n");  
output(a);  
printf("\n");  
/*测试归并排序*/  
printf("归并排序之后的数据:\n");  
p = a;  
MergeSort(a,start,end);  
output(a);  
printf("\n");  
system("pause");  
}

#高精度

加法
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6+10;

vector<int> add(vector<int> & A,vector<int> &B){
	vector<int> C;
	 int t = 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);
		 t /= 10;
	 }
	 if(t)C.push_back(1);
	 return C;
}
int main (){
	string a,b;
	vector<int> A,B;
	cin>> a >> b;
	for(int i = a.size() -1 ;i>=0;i--)A.push_back(a[i]-'0');
	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;
}

减法
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A ,vector<int> &B){
	if(A.size() != B.size()) return A.size() > B.size();
	for(int i = 0 ;i<A.size();i++){
		if (A[i] != B[i])
		return A[i] > B[i];	}
		return true;
	
}
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;//考虑到次位借1
		if(i<B.size()) t -= B[i];
		C.push_back((t+10) %10);
		if(t < 0)t = 1;
		else t = 0;
	}
	while(C.size()>1 && C.back() == 0) C.pop_back();//移除末尾多余的零
	return C;

}
int main(){
	string a,b;
	vector<int> A,B;
	cin >> a >> b;
	for(int i = a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
	for(int i = b.size()-1;i>=0;i--)B.push_back(b[i]-'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++){
		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>
using namespace std;
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();
	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 << r << endl;
	return 0;
}
  1. using namespace std;:引入标准命名空间,使得可以使用如 vector 等标准库中的类型和函数。
  2. 定义 vector<int> C;:创建一个整数向量 C,用于存储商的结果。
  3. int &r = 0;:声明并初始化 r 为整数引用,初始值为0。r 会在函数中被用来存储计算过程中产生的余数。
  4. for(int i = A.size() - 1; i >= 0; i--) {:从 A 的最后一个元素开始,倒序遍历 A
  5. r = r * 10 + A[i];:将当前元素 A[i] 加到 r 的左侧(相当于乘以10),然后将结果存回 r
  6. C.push_back(r / b);:计算 r 除以 b 的商,并将其添加到 C 的末尾。
  7. r %= b;:取 r 除以 b 的余数,更新 r 的值。
  8. reverse(C.begin(),C.end());:反转 C 向量的顺序,因为我们在遍历时是从后向前计算的。
  9. while(C.size() > 1 && C.back() == 0) C.pop_back();:如果 C 非空且最后一个元素是0,就删除这个元素,直到 C 只剩下非0元素或为空。
  10. return C;:返回处理后的向量 C

总的来说,这个函数实现了将 A 中的每个数字(从大到小)转换成除以 b 的商的序列,并移除了多余的0。
#前缀和

一维
#include <iostream>
using namespace std;
const int N = 100010;
int n,m;
int s[N];
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=n;i++)scanf("%d",&s[i]);
	for(int i = 1;i<=n;i++)s[i] += s[i-1];
	while(m--){
		int l , r;
		scanf("%d%d",&l,&r);
		printf("%d\n",s[r]-s[l-1]);
		
	}
	return 0;
}
二维
#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",&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;
}

#差分

#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]);
	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;


}

差分矩阵
![[Pasted image 20240706124131.png]]

二维差分
#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++)
	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++)
	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++)
	printf("%d",b[i][j]);
	puts("");
}
return 0;
}
**一维前缀和**

s[i] += s[i-1]

**二维前缀和**

s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]

**差分**

b[l] += a[i]
b[r+1] += a[i]
b[i] += b[i-1]

**二维差分**

b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] -= c;
b[i][j] = b[i-1][j] + b[i][j-1]-b[i-1][j-1];

A+B看题解复习算法

#双指针
![[Pasted image 20240706154749.png]]

最长连续不重复子序列
#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;
}

#位运算
![[Pasted image 20240706155641.png]]
![[Pasted image 20240706155816.png]]

#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++;
		cout << res << endl;
	}
return 0;

}

你提到的这段代码实际上是在统计一个整数的二进制表示中有多少个1。这就是所谓的“汉明重量”或“汉明重量计算”。让我们逐步解析这段代码,并理解为什么它在每次循环中要减去最低位的1。

代码解释

int x;
cin >> x;
int res = 0;
while(x) {
    x -= lowbit(x);
    res++;
}

lowbit(x) 函数

你可能没有完全展现代码,但通常 lowbit(x) 是一个返回 x 的最低位 1 所在的位置(以位表示)的函数,通常实现为:

int lowbit(int x) {
    return x & -x;
}

程序步骤解析

  1. 输入 x: 从标准输入读取一个整数 x
  2. 初始化计数器 res 为0: 这个计数器用于记录 x 中1的个数。
  3. 循环处理:
    • 每次减去最低位的1: x -= lowbit(x),这个操作实际上是清除 x 的最低位的1。例如,对于 x = 18(二进制为 10010),lowbit(x)2(二进制为 10),因此第一次循环中,x 变为 16(二进制为 10000)。
    • 递增计数器 res: 每次清除一个最低位的1,就意味着找到了一个1,因此 res 递增1。
  4. 循环终止: 当 x 变为 0 时,循环结束,此时 res 的值就是 x 中1的个数。

为什么减去最低位的1不会导致错误

每次执行 x -= lowbit(x) 只会清除 x 中的最低位的1,不会影响到其他位的1。例如,对于 x = 18(二进制为 10010):

  • lowbit(18) 返回 2(二进制为 10)。
  • 18 - 2 = 16,二进制为 10000

你可以看到,原数的最低位1被成功清除,而其他位的1不受影响。这操作是安全的,且有效地确保每次只处理一个1。

示例

让我们手工计算一下,看看如何工作:

假设 x = 19(二进制 10011):

  • 初始 x = 19res = 0
  • 第一次循环:
    • lowbit(19) = 1(二进制 1
    • x -= 1, x = 18(二进制 10010), res = 1
  • 第二次循环:
    • lowbit(18) = 2(二进制 10
    • x -= 2, x = 16(二进制 10000), res = 2
  • 第三次循环:
    • lowbit(16) = 16(二进制 10000
    • x -= 16, x = 0, res = 3

最终,res = 3,正好是 19 的二进制表示中1的个数。

总结

这段代码有效地统计了一个整数 x 的二进制表示中1的个数,利用了位操作中的 lowbit 函数,每次清除最低位的1,这一过程确保每次循环都处理一个1,直到 x 变为0。
![[Pasted image 20240706160207.png]]
#离散化
对其做映射
![[Pasted image 20240710152953.png]]

![[Pasted image 20240706160328.png]]

题目

![[Pasted image 20240706160759.png]]
思路:
![[Pasted image 20240710153222.png]]

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;//操作有两个数用pair
const int N = 300010;//离散化之后要三倍,插入查询一共30个坐标
int n,m;
int a[N],s[N];//a为存储的数,s为我们的前缀和
vector<int> alls;
vector<PII> add,query;一个插入,一个求前缀和
//- 函数用于在`alls`数组中查找某个值`x`的位置。使用二分查找算法。
int find(int 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;


}
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;
}

alls数组的作用

alls数组的主要作用是用于离散化。离散化的目标是将可能范围很广的数映射到相对紧凑的区间,从而简化数组处理和节省空间。下面具体说明alls在代码中的作用:

  1. 收集所有出现的数值

    • 在需要离散化的数值(即插入操作和查询操作中的所有涉及的数)被收集到alls中。

    alls.push_back(x); // 添加插入操作的数值 alls.push_back(l); // 添加查询操作的数值 alls.push_back(r); // 添加查询操作的数值

  2. 去重并排序

    • 将所有数值进行排序,然后用uniqueerase去除重复元素,确保alls中的数值是唯一且有序的。

    sort(alls.begin(), alls.end()); alls.erase(unique(alls.begin(),alls.end()),alls.end());

  3. 利用alls进行离散化映射

    • 使用find函数通过二分查找将原始数值映射到alls中相应的索引位置,使得所有的插入和查询操作都可以在一个紧凑的索引范围内进行。这是通过find函数来实现的。

    int x = find(item.first); // 将原始的数通过find映射到在alls中的位置 a[x] += item.second; // 使用离散化后的索引进行操作

通过离散化和alls数组的使用,代码可以高效地处理插入和带区间查询的问题,即使初始的数值范围很广。这样避免了直接操作可能极大甚至稀疏的数组空间,提高了效率。
unique:
![[Pasted image 20240710162755.png]]

![[Pasted image 20240706164553.png]]

美团的格子染色
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 100010;
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});
	st = seg.first,ed = seg.second;
	}
	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;
}

习题课

#第个k数
![[Pasted image 20240708084424.png]]

#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];
	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;//从中间分两种情况
	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;
}

#逆序对的数量

题目要求

该代码要求:

  1. 输入一个整数 n,表示数组的长度。
  2. 输入 n 个整数,存入数组 q 中。
  3. 输出数组 q 中的逆序对数量。

代码解析和运行流程:

  1. 输入步骤

    • 用户输入整数 n 表示数组长度。
    • 用户输入 n 个整数存入数组 q
  2. 计算逆序对

    • 调用递归函数 merge_sort(0, n - 1) 来排序数组并计算逆序对数量。
  3. 输出结果

    • 打印返回的逆序对数量。

示例

输入:

5
3 2 1 5 4

输出:

4

解析:

  • 数组 [3, 2, 1, 5, 4] 共有 4 个逆序对:
    • (3, 2)
    • (3, 1)
    • (2, 1)
    • (5, 4)
#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){
//因为temp中间会被覆盖,所以可以不对其初始化
	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;
	}
	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;
}

res += mid - i + 1; 的含义

在归并过程中,当左侧数组中的元素 q[i] 大于右侧数组中的元素 q[j] 时,会产生逆序对。具体原因如下:

  • 左侧数组 q[l..mid] 和右侧数组 q[mid+1..r] 已经分别排序。
  • q[i] > q[j],这意味着 q[i]q[j] 组成了一个逆序对。
  • 更进一步,q[i+1], q[i+2], …, q[mid] 都大于 q[j],因为左侧数组是以升序排序的。

因此,此时产生的逆序对数量应当增加 mid - i + 1

  • i 是当前左侧数组的索引。
  • mid 是左侧数组的上界。
  • mid - i + 1 表示从 imid 的所有元素数量。

举个例子,假设左侧数组 [1, 3, 5] 和右侧数组 [2, 4, 6],在某次循环中:

  • i 指向 3j 指向 2
  • 由于 3 > 23 及其之后的数字(即 [3, 5])都大于 2,形成逆序对 (3, 2)(5, 2)
  • 此时,会发现有 mid - i + 1 = 2 个逆序对,累加到 res 中。
    #数的三次方根
    ![[Pasted image 20240708093855.png]]
#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;
}

#一维前缀和
![[Pasted image 20240708094547.png]]

#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;
}

#二维前缀和
![[Pasted image 20240708095952.png]]
![[Pasted image 20240708100049.png]]
![[Pasted image 20240708100643.png]]

#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",&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;
	
}

#差分题目
![[Pasted image 20240708101529.png]]
![[Pasted image 20240708102108.png]
![[Pasted image 20240708102047.png]]![[Pasted image 20240708102118.png]]

#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]);//对b进行初始化,使其都为a
	 while(m--)
	 {
		 int l ,r,c;
		 cin >> l >> r >>c;
		 insert(l,r,c);
		 //改变b
	 }
	 for(int i = 1;i<=n;i++)a[i] = a[i-1] + b[i];//改变a使其为a改变后的前缀和
	 for(int i = 1;i<=n;i++)printf("%d",a[i]);
	 puts("");
	 return 0;
 }

#二维差分题目
![[Pasted image 20240708103704.png]]
![[Pasted image 20240708103715.png]]

#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,&x2,&y1,&y2,&c);
	insert(x1,x2,y1,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];
	printf("%d",a[i][j]);
	puts("");
	return 0;



}
;