基于分治来做的
1.暴力解法
2.优雅解法
ex: 3 1 2 3 5
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + 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);
}
785. 快速排序 - AcWing题库
给定你一个长度为 n 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼1091∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5 3 1 2 4 5
输出样例:
1 2 3 4 5
#include
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+r>>1];
int i = l - 1;
int 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;
}
786. 第k个数 - AcWing题库
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。
输入格式
第一行包含两个整数 n 和 k。
第二行包含 n 个整数(所有整数均在 1∼10^9 范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k 小数。
数据范围
1≤n≤100000,
1≤k≤n输入样例:
5 3 2 4 1 5 3
输出样例:
3
#include
using namespace std;
const int N = 1e6+10;
int n;
int k;
int q[N];
void quick_sort(int q[],int l,int r)
{
if(l >= r)
{
return;
}
int x = q[l+r>>1];
int i = l-1;
int 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);
scanf("%d",&k);
for(int i = 0;i < n;i++)
{
scanf("%d",&q[i]);
}
quick_sort(q,0,n-1);
printf("%d",q[k-1]);
}
基于分治来做
设置两个指针,分别指向两个数组的第一个位置,由于我们已经递归排序了两边数组,所以此时两个数组都是有序的,那么不难得出,我们两个指针指向的值都是这两个数组的最小值,此时我们比较两个指针指向的值,将较小的一个放到我们的res 数组中,然后指针+1,重复此操作,直到一个指针到终点,退出循环,再将剩余数据补充到 res 中
left 数组:1 3 5 7 9
right 数组:2 4 5 8 10
此时两个指针分别指向 left[0] 和 right[0]
left[0] < right[0] --> res[0] = left[0] ,left指针++
left[1] > right[0] --> res[1] = right[0],right指针++
以此类推
最后 res 数组为 = {1,2,3,4,5,5,7,8,9,10}
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];
}
787. 归并排序 - AcWing题库
给定你一个长度为 n 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼10^9范围内),表示整个数列。
输出格式
输出共一行,包含 nn 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5 3 1 2 4 5
输出样例:
1 2 3 4 5
#include
using namespace std;
const int N = 100010;
int n;
int q[N];
int tmp[N];
void merge_sort(int q[],int l,int r)
{
if(l >= r)
{
return;
}
int mid =(l+r)/2;
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(int 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;
}
788. 逆序对的数量 - AcWing题库
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i
a[j],则其为一个逆序对;否则不是。 输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000
数列中的元素的取值范围 [1,10^9][1,109]。输入样例:
6 2 3 4 5 6 1
输出样例:
5
1.左半边内部的逆序对数量: merge_sort(L,mid)
2.右半边内部的逆序对数量: merge_sort(mid+1,R)
3.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYWAE0ZE-1664524287527)(C:\Users\ShibuyaKanon\AppData\Roaming\Typora\typora-user-images\image-20220917214136290.png)]
这题最大逆序对数量是 5*10^9 会报int错,得用 long long
#include
using namespace std;
const int N = 100010;
typedef long long LL;
int n;
int q[N];
int 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 i = l,j = mid + 1,k = 0;
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(i = l,j = 0;i <= r;i++,j++)
{
q[i] = tmp[j];
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d",&q[i]);
}
cout << merge_sort(0,n-1);
}
如果有单调性的话 --> 我可以二分,反之不然
整个区间可以一分为二,我们定义了一个性质,右半边是满足这个性质的,但是左半边不满足
二分可以寻找这个性质的边界
如何二分左半部分的边界点:
mid = l + r +1>> 1
if(check(mid)) -> true:答案处于 [mid,r] -> 更新区间 l = mid else 答案处于 [l,mid-1] -> 更新区间 r = mid -1;
如何二分右半部分的边界点
mid = 1+r >> 1
if(check(mid)) -> true:答案处于 [l,mid] -> 更新区间 r = mid else 答案处于 [mid+1,r] -> 更新区间 l = mid+1;
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
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]被划分成[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 l;
}
789. 数的范围 - AcWing题库
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回
-1 -1
。输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回
-1 -1
。数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000输入样例:
6 3 1 2 2 3 3 4 3 4 5
输出样例:
3 4 5 5 -1 -1
该题的 check:
#include
using namespace std;
const int N = 100010;
int n;
int 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{
printf("%d ",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;
}
}
printf("%d\n",l);
}
}
return 0;
}
当我们的范围足够小的时候,我们就可以认为我们找到了答案
例如 r - l <= 10-6 ,我们认为他已经足够小,因为有整除,所以不需要处理边界问题
790. 数的三次方根 - AcWing题库
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
#include
using namespace std;
int main()
{
double x;
cin >> x;
double l = -100,r = 100;
while(r-l>1e-8)
{
double mid = (l+r)/2;
if(mid*mid*mid>=x)
{
r = mid;
}else{
l = mid;
}
}
printf("%.6lf",l);
return 0;
}
大整数如何存储? --每一位存到数组里
例如:123456789 第 0 位存谁? – 存9
因为 如果 0位存最后一位,需要乘法的时候,在数组末尾添加数字要比数组开端添加数字方便
模拟我们人加减法的过程
例如 123 + 89,我们先算3 + 9 = 11,进位,再算 3+8 = 11,再进位得 2,所以我们的答案是 212
那么假设我们有两个大整数分别存储在了 A[] , 和 B[] 中
$$
A_3 A_2A_1A_0\
那么我们可以推导出 每一位Ci = Ai + Bi + t,t为上一次的进位
// C = A + B, A >= 0, B >= 0
vector add(vector &A, vector &B)
{
if (A.size() < B.size()) return add(B, A);
vector C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
// C = A - B, 满足A >= B, A >= 0, B >= 0
vector sub(vector &A, vector &B)
{
vector C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
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;
}
791. 高精度加法 - AcWing题库
给定两个正整数(不含前导 0),计算它们的和。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的和。
数据范围
1≤整数长度≤100000
输入样例:
12 23
输出样例:
35
#include
#include
#include
using namespace std;
const int N = 1e6+10;
vector add(vectorA,vectorB)
{
vector C;
int t;
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;
cin >> a >> b;
vector 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--)
{
cout << C[i];
}
}
$$
A_3 A_2A_1A_0\
那我们可归纳为
C i = A i − B i − t = { A i − B i − t , C i > = 0 A i − B i + 10 − t , C i < 0 C_i = A_i-B_i-t= \begin{cases}A_i-B_i-t,Ci>=0\\A_i-B_i+10-t,C_i<0\end{cases} Ci=Ai−Bi−t={Ai−Bi−t,Ci>=0Ai−Bi+10−t,Ci<0
A − B = { A − B , A > = B − ( B − A ) , A < = B A - B = \begin{cases} A-B,A>=B\\-(B-A),A<=B\end{cases} A−B={A−B,A>=B−(B−A),A<=B
// C = A - B, 满足A >= B, A >= 0, B >= 0
vector sub(vector &A, vector &B)
{
vector C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
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;
}
792. 高精度减法 - AcWing题库
给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围
1≤整数长度≤10^5
输入样例:
32 11
输出样例:
21
#include
#include
#include
using namespace std;
bool cmp(vector A,vector B)
{
if(A.size()!=B.size())
{
return A.size()>B.size();
}else{
for(int i = A.size()-1;i >= 0;i--)
{
if(A[i]!=B[i])
{
return A[i] > B[i];
}
}
}
return true;
}
vector sub(vector&A,vector&B)
{
vector C;
int t;
for(int i = 0,t = 0;i 1&&C.back()==0)
{
C.pop_back();
}
return C;
}
int main()
{
string a,b;
cin >> a >> b;
vector 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');
}
if(cmp(A,B))
{
auto C = sub(A,B);
for(int i = C.size()-1;i >= 0;i--)
{
cout << C[i];
}
}else{
auto C = sub(B,A);
cout << "-";
for(int i = C.size()-1;i >= 0;i--)
{
cout << C[i];
}
}
}
$$
A_3 A_2A_1A_0\
a\\
------\
C_3C_2C_1C_0
$$
A = 123 B=12
C0 = (3x12)%10 = 6 t1 = (3x12)/10 = 3
C1 = (2x12+t)%10 = 7 t2 = 2
答案是:1476
// C = A * b, A >= 0, b >= 0
vector mul(vector &A, int b)
{
vector 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;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
793. 高精度乘法 - AcWing题库
给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B 的值。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共一行,包含 A×B 的值。
数据范围
1≤A的长度≤100000
0≤B≤10000输入样例:
2 3
输出样例:
6
#include
#include
#include
using namespace std;
vector mul(vector &A, int b)
{
vector 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;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
int b;
string a;
vector A;
cin >> a >> b;
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--)
{
cout << C[i];
}
}
例如:11√1234
首先看第一位,1除以11,显然不够,上0,余数是1,我们把余数乘10+下一位的2,得到12
,得到1,余数为1,以此类推得到答案是112
// A / b = C ... r, A >= 0, b > 0
vector div(vector &A, int b, int &r)
{
vector 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;
}
794. 高精度除法 - AcWing题库
给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
1≤A的长度≤100000
1≤B≤10000,
B 一定不为 0输入样例:
7 2
输出样例:
3 1
#include
#include
#include
#include
using namespace std;
vector div(vector &A,int b,int &r)
{
vector 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 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--)
{
cout << C[i];
}
cout << endl;
cout << r;
}
我们假定有一个长度为 n 的数组
a 1 a 2 a 3 . . . a n a_1 a_2a_3...a_n a1a2a3...an
定义前缀和数组为:
S i = a 1 + a 2 + . . . + a n , s 0 = 0 S_i=a_1+a_2+...+a_n,s_0 = 0 Si=a1+a2+...+an,s0=0
1.如何求 si
for(int i = 1;i <= n;i++)
{
s[i] = s[i-1]+a[i];
}
2.作用
能快速地求出来,数组中一段数的和
例如 求 [l,r] 的和,用前缀和数组来求即为 Sr - Sl-1
S r = a 1 + a 2 + a 3 + . . . + a l − 1 + a l + . . . + a r S l − 1 = a 1 + a 2 + a 3 + . . . + a l − 1 那么 S r − S l − 1 = a l + a l + 1 + . . . + a r S_r = a_1+a_2+a_3+...+a_{l-1}+a_l+...+a_r\\ S_{l-1}=a_1+a_2+a_3+...+a_{l-1}\\ 那么 S_r-S_{l-1} = a_l+a_{l+1}+...+a_r Sr=a1+a2+a3+...+al−1+al+...+arSl−1=a1+a2+a3+...+al−1那么Sr−Sl−1=al+al+1+...+ar
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
795. 前缀和 - AcWing题库
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n
1≤n,m≤100000
−1000≤数列中元素的值≤1000输入样例:
5 3 2 1 3 6 4 1 2 1 3 2 4
输出样例:
3 6 10
#include
using namespace std;
const int N = 100010;
int a[N],s[N];
int n,m;
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];
}
for(int i = 0;i < m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}
假定原数组为 a i , j , 前缀和数组为 S i , j S i , j 表示以( i , j )这个点左上角所有元素的和我们可先减去左半边矩阵的和 S x 2 , y 2 − S x 2 , y 1 − 1 再减去上半部分的矩阵的和 S x 2 , y 2 − S x 2 , y 1 − 1 − S x 1 − 1 , y 2 但是我们发现,左上角的小正方形被减了两次,加回来 S x 2 , y 2 − S x 2 , y 1 − 1 − S x 1 − 1 , y 2 + S x 1 − 1 , y 1 − 1 假定原数组为 a_{i,j},前缀和数组为S_{i,j}\\ S_{i,j}表示以(i,j)这个点左上角所有元素的和 我们可先减去左半边矩阵的和\\ S_{x_2,y_2} - S_{x_2,y_1-1}\\ 再减去上半部分的矩阵的和\\ S_{x_2,y_2} - S_{x_2,y_1-1}-S_{x_1-1,y_2}\\ 但是我们发现,左上角的小正方形被减了两次,加回来\\ S_{x_2,y_2} - S_{x_2,y_1-1}-S_{x_1-1,y_2}+S_{x_1-1,y_1-1} 假定原数组为ai,j,前缀和数组为Si,jSi,j表示以(i,j)这个点左上角所有元素的和我们可先减去左半边矩阵的和Sx2,y2−Sx2,y1−1再减去上半部分的矩阵的和Sx2,y2−Sx2,y1−1−Sx1−1,y2但是我们发现,左上角的小正方形被减了两次,加回来Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1
如何推导 S[i,j]
for(int i = 1;i
//S[i, j] = 第i行j列格子左上部分所有元素的和
//以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
796. 子矩阵的和 - AcWing题库
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q 行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000
1≤q≤200000
1≤x1≤x2≤n
1≤y1≤y2≤m
−1000≤矩阵内元素的值≤1000
#include
const int N = 1010;
int n,m,q;
int a[N][N],s[N][N];
using namespace std;
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,x2,y1,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;
}
差分是前缀和的逆运算
给定一个 长度为 n 的a 数组,构造一个 b 数组,使得:
a i = b 1 + b 2 + b 3 + . . . + b i b 1 = a 1 b 2 = a 2 − a 1 b n = a n − a n − 1 a_i=b_1+b_2+b_3+...+b_i\\ b_1=a_1\\ b_2=a_2-a_1\\ b_n=a_n-a_{n-1} ai=b1+b2+b3+...+bib1=a1b2=a2−a1bn=an−an−1
b就称为 a 的差分,a就称为b的前缀和
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
797. 差分 - AcWing题库
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r][l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
1≤n,m≤100000
1≤l≤r≤n
−1000≤c≤1000
−1000≤整数序列中元素的值≤1000输入样例:
6 3 1 2 2 1 2 1 1 3 1 3 5 1 1 6 1
输出样例:
3 4 5 3 4 2
#include
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;
}
b [ x 1 ] [ y 1 ] + = c b [ x 2 + 1 ] [ y 1 ] − = c b [ x 1 ] [ y 2 + 1 ] − = c b [ x 2 + 1 ] [ y 2 + 1 ] + = c b [ i ] [ j ] 记录的就是和相邻元素的差 b[x_1][y_1]+=c\\ b[x_2+1][y_1]-=c\\ b[x1][y2+1]-=c\\ b[x_2+1][y_2+1]+=c\\ b[i][j] 记录的就是和相邻元素的差 b[x1][y1]+=cb[x2+1][y1]−=cb[x1][y2+1]−=cb[x2+1][y2+1]+=cb[i][j]记录的就是和相邻元素的差
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
798. 差分矩阵 - AcWing题库
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1)和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 cc。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000
1≤q≤100000
1≤x1≤x2≤n
1≤y1≤y2≤m
−1000≤c≤1000
−1000≤矩阵内元素的值≤100−1000≤矩阵内元素的值≤1000输入样例:
3 4 3 1 2 2 1 3 2 2 1 1 1 1 1 1 1 2 2 1 1 3 2 3 2 3 1 3 4 1
输出样例:
2 3 4 1 4 3 4 1 2 2 2 2
#include
using namespace std;
const int N = 1010;
int n,m,q;
int a[N][N],b[N][N];
void insert(int x1,int x2,int y1,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,i,j,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,x2,y1,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]);
}
printf("\n");
}
return 0;
}
双指针的核心作用 就是 可以优化
所有双指针算法都是 O(n) 的
优化的本质是 找到 i 和 j 的规律
for(int i = 0;i <= n;i++)
{
for(int j = 0;j <= n;j++)
{
}
}
//O(n^2) 的复杂度
//使用 双指针算法 可以将其优化到 O(n)
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
比如我们给定一个形同 abc def ghi 的字符串,要求把每个单词输出
那么我们可以定义两个指针,第一个指针指向单词开头,当第二个指针没有走到字符串末尾并且所指向的字符不为空格的时候,他自增。
在输出结束后,让 i = j,来跳过整个已经输出的区间。
#include
#include
using namespace std;
int main()
{
char str[1000];
gets(str);
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;
}
}
799. 最长连续不重复子序列 - AcWing题库
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤10^5
输入样例:
5 1 2 2 3 5
输出样例:
3
//朴素做法
for(int i = 0;i < n;i++)
{
for(int j = 0;j < n;j++)
{
if(check(j,i))
{
res = max(res,i-j+1);
}
}
}
//双指针做法
#include
using namespace std;
const int N = 1e6+10;
int q[N];
int s[N];
int n;
int res = 0;
int main()
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d",&q[i]);
}
res = 0;
for(int i = 0,j=0;i < n;i ++)
{
s[q[i]]++;
while(s[q[i]]>1)
{
s[q[j]]--;
j++;
}
res = max(res,i-j+1);
}
cout << res << endl;
return 0;
}
800. 数组元素的目标和 - AcWing题库
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i和 j。
数据范围
数组长度不超过 105。
同一数组内元素各不相同。
1≤数组元素≤109输入样例:
4 5 6 1 2 4 7 3 4 6 8 9
输出样例:
1 1
#include
using namespace std;
const int N = 1e5+10;
int a[N];
int b[N];
int n,m,x;
int main()
{
scanf("%d%d%d",&n,&m,&x);
for(int i = 0;i < n;i++)
{
scanf("%d",&a[i]);
}
for(int i = 0;i < m;i++)
{
scanf("%d",&b[i]);
}
for (int i = 0, j = m - 1; i < n; i ++ )
{
while (j >= 0 && a[i] + b[j] > x)
{
j -- ;
}
if (j >= 0 && a[i] + b[j] == x)
{
cout << i << ' ' << j << endl;
}
}
return 0;
}
2816. 判断子序列 - AcWing题库
给定一个长度为 nn 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm。
请你判断 a 序列是否为 b 序列的子序列。
子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}的一个子序列。
输入格式
第一行包含两个整数 n,m。
第二行包含 n 个整数,表示 a1,a2,…,an。
第三行包含 m 个整数,表示 b1,b2,…,bm。
输出格式
如果 a 序列是 b 序列的子序列,输出一行
Yes
。否则,输出
No
。数据范围
1≤n≤m≤105
−109≤ai,bi≤109输入样例:
3 5 1 3 5 1 2 3 4 5
输出样例:
Yes
#include
using namespace std;
const int N = 1e5+10;
int n,m;
int a[N],b[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++)
{
scanf("%d",&a[i]);
}
for(int i = 0;i < m;i++)
{
scanf("%d",&b[i]);
}
int j = 0;
for(int i = 0;i < n;i++)
{
for(;j <= m;j++)
{
if(a[i] == b[j])
{
//cout << i << " " << j << endl;
j++;
break;
}
}
//cout << j << " ";
if(j >= m+1 && i < n)
{
cout << "No";
return 0;
}
}
cout << "Yes";
return 0;
}
求 整数 n 二进制表示里,第 k 位数字是几?
n = 15 = ( 1111 ) 2 n = 15 = (1111)_2 n=15=(1111)2
例如输出 10 的二进制表达
#include
int main()
{
int n = 10;
for(int k = 3;k >= 0;k--)
{
cout << (n >> k)&1;
}
return 0;
}
lowbit(x) 作用:返回 x 的最后一位 1
例如 x = 1010,lowbit(x) = 10
x = 101000,lowbit(x) = 1000
x & (-x) = x & (~x+1)
x = 1010…100…0
~x=0101…011…1
~x+1=0101…100…0
x & (~x+1) = 0…010…0
可以利用 lowbit 返回 x 二进制 1 的个数
原理:每一次把最后一位1减掉,当x = 0的时候,里面就没有1了,减了多少次就是有多少个1
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
801. 二进制中1的个数 - AcWing题库
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
数据范围
1≤n≤100000
0≤数列中元素的值≤109输入样例:
5 1 2 3 4 5
输出样例:
1 1 2 1 2
#include
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;
}
}
假如我们有一对数值 范围是 0 - 109
但是个数比较少,只有比如 105的数字,我们需要以这些值位下标来做
所以我们需要这个序列映射到 从 0 开始的连续的自然数
比如我们假定有数组:a[] :1,3,100,200,500000
我们将 1 映射到 0,3 映射到 1,100 映射到 2,200 映射到 3,500000 映射到 4,这个过程就是离散化
vector 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
}
802. 区间和 - AcWing题库
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r][l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−109≤x≤109
1≤n,m≤105
−109≤l≤r≤109
−10000≤c≤10000输入样例:
3 3 1 2 3 6 7 5 1 3 4 6 7 8
输出样例:
8 0 5
#include
#include
#include
using namespace std;
const int N = 300010;
int n,m;
int a[N],s[N];
typedef pair PLL;
vector alls;
vector add,query;
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);
int r = find(item.second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}
给我们很多很多区间,这两个区间有交集,我们合并成一个区间
例如 [1,9] 和 [3,13] 可以合并为 [1,13]
对于 i : st 和 ed 不变
对于 ii:ed = edi,st不变
对于 iii 不变
我们假定有区间如下:
[ 1 , 2 ] , [ 2 , 4 ] , [ 5 , 6 ] , [ 7 , 8 ] , [ 7 , 9 ]
第一次合并区间后得到
[ 1 , 4 ] , [ 5 , 6 ] , [ 7 , 8 ] , [ 7 , 9 ]
因为 [ 5 , 6 ] 大于 ed(4) 所以第一个区间和以后所有的区间不会有任何交集,把 [ 1 , 4 ] 放入答案
同理 [ 5 , 6 ] 和以后所有的区间也不会有任何交集,放入答案中
故而最后答案为 [ 1 , 4 ] , [ 5 , 6 ] , [ 7 , 9 ]
// 将所有存在交集的区间合并
void merge(vector &segs)
{
vector 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;
}
803. 区间合并 - AcWing题库
给定 n 个区间 [li,ri][li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000,
−109≤li≤ri≤109输入样例:
5 1 2 2 4 5 6 7 8 7 9
输出样例:
3
#include
#include
#include
using namespace std;
typedef pair PII;
const int N = 1000010;
int n;
vector segs;
void merge(vector &segs)
{
vector 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(seg.second,ed);
}
}
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;
}