AcWing基础算法
代码模板要理解背过
课下把思想搞懂,能默写出来
代码删掉重复写三四遍就差不多
第一节课
算法模板:
void quick_sort(int q[], int l, int r) {
if (l >= r) {
return;
}
int i = l - 1, j = r + 1, x = q[(l + r) / 2];
while (i < j) {
do {
i++;
} while (q[i] < x);
do {
j--;
} while (q[j] > x);
if (i < j) {
//TODO
swap(q[i], q[j]); // swap()函数
}
quick_sort(q, l, j); // 为什么选择j ?
quick_sort(q, j + 1, r);
}
}
//交换两个数
if(i<j){
//TODO
int t=q[i];
q[i]=q[j];
q[j]=t;
}
#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 i = l - 1, j = r + 1, x = q[(l + r) / 2];
while (i < j) {
do {
i++;
} while (q[i] < x);
do {
j--;
} while (q[j] > x);
if (i < j) {
//TODO
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++) {
//TODO
scanf("%d", &q[i]);
}
quick_sort(q, 0, n - 1);
for (int i = 0; i < n; i++) {
//TODO
printf("%d ", q[i]);
}
return 0;
}
中间划分,合并,扫尾,还原
算法模板:
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;// 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++];
}
// tmp[]数组元素复制存储到原数组q[],还原
for(i=l,j=0;i<=r;i++,j++){
q[i]=tmp[j];
}
}
快速排序是稳定的
归并排序是不稳定的
若想要快排稳定,只要让快排数组中每个数都不同,设置一个双元组,把下标也放进去,形成pair()
#include
using namespace std;
const int N = 100010;
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++];
}
// tmp[]数组元素复制存储到原数组q[]
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++) {
//TODO
scanf("%d", &q[i]);
}
merge_sort(q, 0, n - 1);
for (int i = 0; i < n; i++) {
//TODO
printf("%d ", q[i]);
}
return 0;
}
会有很多边界问题
有单调性的题目一定可以二分
算法模板:
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) / 2; // 第一种不需要补上加一
if (check(mid)) { // check()判断mid是否满足性质
//TODO
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
// 情况二:区间[l,r]被划分成[l,mid-1]和[mid,r]时使用:
int bsearch_2(int l, int r) {
while (l < r) {
//TODO
int mid = (l + r + 1) / 2; // 第二种需要补上加一
if (check(mid)) {
l = mid;
//TODO
} else {
r = mid - 1;
}
}
return l;
}
C++里面的取整是往下取整
一个题目,如果一个区间具有单调性质,那么一定可以二分,但是如果说这道题目没有单调性质,而是具有某种区间性质的话,我们同样可以使用二分.
——yxc总
#include
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) / 2; // 区间[l,r]被划分成区间[l,mid]和[mid+1,r]
if (q[mid] >= x) { // 满足大于等于x的数,从x位置开始所有数都大于等与x
r = mid;
} else {
l = mid + 1;
}
} // 循环结束,l=r
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) / 2; // 区间[l,r]被划分成[l,mid-1]和[mid,r]时使用
if (q[mid] <= x) { //定义x左边所有的数都小于等于x
l = mid; //因为是第二个模板所以需要补上加一
} else {
r = mid - 1;
}
}
cout << l << endl;
}
}
return 0;
}
本质上也是一个边界
算法模板:
bool check(double) { // 检查x是否满足某种性质
}
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;
}
#include
using namespace std;
int main() {
double x;
cin >> x;
// 0.01=0.1*0.1
double l = -10000, r = 1000;
while (r - l > 1e-8) {
double mid = (l + r) / 2 ;
if (mid * mid * mid < x) {
l = mid;
} else {
r = mid;
}
}
printf("%lf", l);
return 0;
}
第二节课
大整数每一位存储到数组里面,高位在前还是低位在前
123456789
第0位应该存1还是存9?
我们第0位存的是个位9,第一位存储十位8
原因:两个整数相加可能会进位,在末尾添加数字很容易,在首位添加数组所有数字往后移,不方便。
模板:
#include
#include
#include
using namespace std;
// C =A+B ,A>=0,B>=0
vector<int> add(vector<int> &A, vector<int> &B) {
if (A.size() < B.size()) {
return add(B, A); // 使得A向量长度大于B向量的长度
}
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i++) {
t = t + A[i];
if (i < B.size()) {
t = t + B[i];
}
C.push_back(t % 10);
t = t / 10; // 整除10是进位的数字,逢十进一
}
if (t) {
C.push_back(t);
}
return C;
}
#include
#include
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;
for (int i = 0; i < A.size() || i < B.size(); i++) {
//i 是有符号整数,A.size()是无符号整数
if (i < A.size()) {
t = t + A[i];
}
if (i < B.size()) {
t = 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; //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--) {
//TODO
B.push_back(b[i] - '0'); // B
}
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--) {
printf("%d", C[i]);
}
return 0;
}
加减存储过程是一样的,因为在计算结果的过程中可能会涉及加减乘除
模板:
// C= A-B 满足(保证较大的数减去较小的数,保证最前面的数不会借位)A>=B, A>=0,B>=0
// 若A 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); // 有两种情况t>=0 就是t 和t<0就t+10 总结为(t + 10) % 10
if (t < 0) {
t = 1;
} else {
t = 0;
}
}
while (C.size() > 1 && C.push_back() == 0) {
C.push_back();
}
return C;
}
#include
#include
#include
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; // A=B
}
// 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()) { // 判断B的位数是否存在,因为B
t -= B[i];
}
C.push_back((t + 10) % 10); // 有两种情况t>=0 就是t 和t<0就t+10 总结为(t + 10) % 10
if (t < 0) { // 判断是否需要借位,t只有两种状态1和0
t = 1;
} else {
t = 0;
}
}
while (C.size() > 1 && C.back() == 0) { // 去掉前导0
C.pop_back();
}
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--) {
//TODO
B.push_back(b[i] - '0'); // B
}
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);
for (int i = C.size() - 1; i >= 0; i--) {
printf("%d", C[i]);
}
}
return 0;
}
模板:
// C=A*b ,A>=0,b>=0 ,把B看作一个整体和A的每个数字相乘,不像普通的乘法
vector<int> mul(vector<int>&A, int b) {
vector C;
int t = 0;
for (int i = 0; i <= A.size() || t; i++) {
//TODO
if (i < A.math()) {
t += A[i] * b;
//TODO
C.push_back(t % 10);
t /= 10;
}
}
while (C.size() > 1 && C.back() == 0) {
C.pop_back();
}
return C;
}
#include
#include
using namespace std;
vector<int> A;
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; // a很长用字符串来存储
int b; // b很短用数组来存储
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--) {
printf("%d", C[i]);
}
return 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();
//TODO
}
return C;
}
#include
#include
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) { // 去掉前导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;
}
作用:快速求出来数组中一段数组元素的和
模板:
公式:
S[i] =a[1]+a[2]+...+a[i];
a[l]+...+a[r]=S[r]-S[l-1]
s[r]=a[1]+a[2]+...+a[l-1]+a[l]+...+a[r]
s[l-1]=a[1]+a[2]+...a[l-1]
把s[0]定义为0,统一处理所有情况
#include
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]); //TODO
}
for (int i = 0; i < n; i++) {
s[i] = s[i - 1] + a[i]; //TODO
}
while (m--) {
int l, r;
scanf("%d%d", &l, &r); //TODO
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
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]
#include
using namespace std;
const int N = 10010;
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;
}
给区间[l,r]中的每一个数加上c
B[i]+=c
B[r+1]-=c
题解:
// 使用差分,时间复杂度为O(1)
#include
using namespace std;
const int N=100010;
int a[N],b[N];
int n,m;
int main () {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
b[i]=a[i]-a[i-1];
}
int l,r,c;
while(m--) {
scanf("%d%d%d",&l,&r,&c);
b[l]+=c; // 从l开始所有元素差分数组都加上c
b[r+1]-=c; // 从r往后的元素已经加上了c,所以要减去c
}
for(int i=1; i<=n; i++) {
a[i]=b[i]+a[i-1]; // 前缀和运算
printf("%d ",a[i]);
}
return 0;
}
模板:
给以(x1,y1)为左上角,(x2,y2)为右下角的子矩阵中的所有元素加上c
S[x1,y1]+=c
S[x2,y1]-=c
S[x1,y2+1]-=c
S[x2+1,y2+1]+=c
798: 差分矩阵
// 差分数组中没有前缀和
#include
#include
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; //右下角部分的矩形全部加上c, x1,y1,x2,y2递增
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++) {
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;
}
#include
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;
}