学习笔记,仅供参考。
如有错误,欢迎探讨。
To Me
1.例题说明
1.整篇笔记的例题都是AcWing上的。
2.例题模块的第一行为例题题号和名称,随后的注释为题目内容,再下面是正确的可以运行的代码。
2.模板说明
1.y总给的模板,或者有我自己的一些修改,但是是正确的。
2.都是伪代码。
3.做题细节总结
1.2分中求mid的时候要用mid = l + (r - l) / 2来做。
2.当输入字符的时候可以用字符数组来代替,因为输入有的时候会有空格,而%c严格读入一个字符,所以有可能把空格读入,导致输入结果不对。%s可以过滤掉空格和回车。
3.memset是以字节赋值的,取数的后八位二进制数,然后把数组里所有数的每个字节都赋值成这样。一般为0或-1。
4.让cin和scanf一样快的方法
cin.tie(0);
ios::sync_with_stdio(false);
5.INT_MAIX一般为0x3f3f3f3f。
一、基础算法
排序
1.快速排序——分治
1.找分界点,一般为数组最左边,最右边,中间,或者随机点。
2.使分界点左边的数都小于等于分界点,右边的数都大于等于分界点。
3.递归
模板:
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
else break;
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
例题:
785 快速排序
//给定你一个长度为 n 的整数数列。
//请你使用快速排序对这个数列按照从小到大进行排序。
//并将排好序的数列按顺序输出。
#include<iostream>
using namespace std;
int n;
const int N = 1e6 + 10;
int q[N];
void quick_sort(int l, int r){
if(l >= r) return;
int x = q[ l + r >> 1], 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(l, j);
quick_sort(j+1, r);
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n; i++){
scanf("%d", &q[i]);
}
quick_sort(0, n-1);
for(int i = 0; i < n; i++){
printf("%d ", q[i]);
}
return 0;
}
2.归并排序——分治
1.找分界点,为数组中间的那个点。
2.递归排序:L->mid,mid+1->R
3.归并——合二为一。
模板:
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 归并排序
//给定你一个长度为 n 的整数数列。
//请你使用归并排序对这个数列按照从小到大进行排序。
//并将排好序的数列按顺序输出。
#include<iostream>
using namespace std;
int n;
const int N = 1e6 + 10;
int q[N], temp[N]
void merge_sort(int l, int r){
if(l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l, j = mid + 1;
int k = 0;
while(i <= mid && j <= r){
if(q[i] <= q[j]) temp[k++] = q[i++];
else temp[k++] = q[j++];
}
while(i <= mid) temp[k++] = q[i++];
while(j <= r) temp[k++] = q[j++];
for(int i = l, j = 0; i <= r; i++, j++){
q[i] = temp[j];
}
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n; i++){
scanf("%d", &q[i]);
}
merge_sort(0, n-1);
for(int i = 0; i < n; i++){
printf("%d ", q[i]);
}
}
二分法
1.整数二分
基础的mid写法为:mid = (l + r) / 2
但这种写法当l和r特别大的时候mid会得到一个负数,所以求mid时一般写成:
mid = l + (r - l) / 2,上取整为mid = l + (r - l + 1) / 2
模板1:
// 区间[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;
}
模板2:
// 区间[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 数的范围
//给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
//对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
//如果数组中不存在该元素,则返回 -1 -1。
#include<iostream>
using namespace std;
int n, q;
const int N = 1e6 + 10;
int arr[N];
int main(){
cin>>n>>q;
for(int i = 0; i < n; i++){
cin>>arr[i];
}
while(q--){
int k;
cin>>k;
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(arr[mid] >= k) r = mid;
else l = mid + 1;
}
if(k != arr[l]) cout<<"-1 -1"<<endl;
else{
cout<<l<<" ";
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l + 1) / 2;
if(arr[mid] <= k) l = mid;
else r = mid - 1;
}
cout<<l<<endl;
}
}
return 0;
}
2.实数(浮点数)二分
一般eps取比题目要求精度多乘0.01就行
模板:
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
例题:
790 数的三次方根
//给定一个浮点数 n,求它的三次方根。
#include<bits/stdc++.h>
using namespace std;
int main(){
double n;
cin>>n;
double l = -10000, r = 10000;
while(r - l > 1e-8){
double mid = l + (r - l) / 2;
if(mid*mid*mid >= n) r = mid;
else l = mid;
}
printf("%lf", l);
return 0;
}
高精度
1.高精度加法
1.用vector去储存两个数,把两个数定义成String,然后拆分每一位给vector。
2.由于在vector后添加数据更为简单,所以在存储时将数的低位放到数组前面。
模板:
vector<int> add(vector<int> &A, vector<int> &B){
vector<int> C;
int temp = 0; //进位数
for(int i = 0; i < A.size() || i < B.size(); i++){
//只要A,B长度有一个没加完,就继续加
if(i < A.size()) temp += A[i];
if(i < B.size()) temp += B[i];
C.push_back(temp % 10); //要用push_back,不能数组用数组下标赋值
temp /= 10;
}
if(temp) C.push_back(temp); //最后一个进位肯定为1
return C;
}
例题:
791 高精度加法
//给定两个正整数(不含前导 0),计算它们的和。
#include<iostream>
#include<vector>
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B){
vector<int> C;
int temp = 0; //进位数
for(int i = 0; i < A.size() || i < B.size(); i++){
//只要A,B长度有一个没加完,就继续加
if(i < A.size()) temp += A[i];
if(i < B.size()) temp += B[i];
C.push_back(temp % 10); //要用push_back,不能数组用数组下标赋值
temp /= 10;
}
if(temp) C.push_back(temp); //最后一个进位肯定为1
return C;
}
int main(){
string a, b;
cin>>a>>b;
vector<int> 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];
return 0;
}
2.高精度减法
默认给的两个整数都是正整数。
A, B, t(下一位找本位借位的数)
t = 0表示没有借位,t = 1表示借位了
t也表示当前位的值。
1.先判断A和B谁大,如果A - B >= 0,那就是A - B;如果A - B < 0,那就是B - A。
2.如果A - B - t >= 0,那结果就是A - B - t;如果A - B - t < 0,那结果就是A - B - t + 10。
模板:
// 判断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;
}
// 高精度减法
// C = A - B, 满足A >= B, A >= 0, B >= 0
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];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();// 去掉前导0
return C;
}
例题:
792 高精度减法
//给定两个正整数(不含前导 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 = A.size()-1; i >= 0; 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;
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;
cin>>a>>b;
vector<int> 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];
}
return 0;
}
3.高精度乘法
高精度*低精度
1.用A的每一位去乘以b,得出来的数%10是当前位的值,/10是进位的值,用一个t既代表了进位,又代表了当前的值,每一轮循环最后再更新t的值,t /= 10。
模板:
// 高精度乘低精度
// C = A * b, A >= 0, b > 0
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;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); //处理前导0
return C;
}
例题:
793 高精度乘法
//给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B 的值。
#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;
}
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');
auto C = mul(A, b);
for(int i = C.size() - 1; i >= 0; i--) cout<<C[i];
return 0;
}
4.高精度除法
高精度/低精度
1.除法与其它三种算法不同,它不需要倒序存储,因为除法是从高位开始除的,但是为了保持和那三种算法的一致,我们还是用倒序存储高精度的数。
2.在处理商C的时候,需要将其反转一下,因为这样可以更好的消去它的前导0,并且不会在主函数输出的时候写法和前三种不同。
模板:
// 高精度除以低精度
// A / b = C ... r, A >= 0, b > 0
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;
}
例题:
794 高精度除法
//给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。
#include<iostream>
#include<vector>
#include<algorithm>
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--) cout<<C[i];
cout<<endl<<r;
return 0;
}
前缀和与拆分
1.一维前缀和
模板:
// 一维前缀和
// S[i] = a[1] + a[2] + ... a[i]
// a[l] + ... + a[r] = S[r] - S[l - 1]
例题:
795 前缀和
// 输入一个长度为 n 的整数序列。
//接下来再输入 m 个询问,每个询问输入一对 l,r。
//对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N], S[N];
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 这里都从下标1开始,因为题目给的l,r都是第几个数,而不是数组下标
for(int i = 1; i <= n; i++) S[i] = S[i - 1] + a[i];
while(m--){
int l, r;
scanf("%d%d", &l, &r);
cout<<S[r] - S[l - 1]<<endl;
}
return 0;
}
2.二维前缀和
模板:
// 二维前缀和
// 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 子矩阵的和
//输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
//对于每个询问输出子矩阵中所有数的和。
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N][N], S[N][N];
int n, m, q;
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);
cout<<S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]<<endl;
}
return 0;
}
3.一维差分
一个思想就是一开始认为a数组和b数组都为0,后面只需要插入一遍就可以了。
to me:看的时候如果看不明白就列一下递推式子看。bn = an - an-1
模板:
// 一维前缀和
// S[i] = a[1] + a[2] + ... a[i]
// a[l] + ... + a[r] = S[r] - S[l - 1]
例题:
797 差分
//输入一个长度为 n 的整数序列。
//接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
//请你输出进行完所有操作后的序列。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c){
b[l] += c; // 下标为l的差分数组+c就可以改变其前缀和数组l及以后的数了
b[r + 1] -= c; // 对应的要在区间范围外的数减去c
}
int main(){
cin>>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);
}