学习基础算法
排序
快排
找一个基准x,然后将小于x的拉到其左侧,大于x的拉到其右侧,然后递归。其核心思想是分治。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #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+r)/2 ],i=l-1 ,j=r+1 ; while (i<j){ while (x>q[++i]); while (x<q[--j]); 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]); }
归并排序
核心也是分治,不过和快排不同的是自低向上排序。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include<iostream> using namespace std; const unsigned maxn = 100010; int temp[maxn]; int arr[maxn]; void gsort(int arr[],int l,int r){ if( l>=r)return; int mid = l+r >> 1; gsort(arr,l,mid); gsort(arr,mid+1,r); int i=l,j=mid+1,k=l; while(i <= mid && j <= r){ if(arr[i]<arr[j]) temp[k++] = arr[i++]; else temp[k++] = arr[j++]; } while(i <= mid )temp[k++]=arr[i++]; while(j <=r )temp[k++] = arr[j++]; // for(int i=0;i >= 0 && i<= r; i++) printf("%d",arr[i]); // //printf("\t"); // // for(int i=0;i >= 0 && i<= r; i++) printf("%d",temp[i]); // printf("\tl=%d",l); // printf("\n"); i = l; while(i <= r){ arr[i]=temp[i]; i++; } } int main(){ int n; scanf("%d",&n); for(int i=0; i<n; i++)scanf("%d",&arr[i]); gsort(arr,0,n-1); for(int i=0; i<n; i++) cout<< arr[i] << ' ' ; }
练习
逆序对数量 https://www.acwing.com/solution/content/5508/
两个元素分别在左和右比较难想。
查找算法
二分
重点是找到区分条件。整数二分还要注意边界问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include<iostream> using namespace std; const int N = 1e5 + 10; int arr[N]; int main(){ int n,q; scanf("%d%d",&n,&q); for(int i=0; i<n; i++) scanf("%d",&arr[i]); while(q--){ int x; scanf("%d",&x); int l = 0,r = n-1; while(l < r){ int mid = l+r >> 1; if(arr[mid] < x)l = mid + 1; else r = mid; } if(arr[l] != x)cout << "-1 -1" << endl; else { cout << l; l = 0, r = n-1; while (l < r){ int mid = l+r+1 >> 1; if(arr[mid] > x) r = mid - 1; else l = mid; } cout << ' ' << l << endl; } } return 0; }
#
高精度
加法
用数组表示一个大数:最低位放在数组的0位置,次低位放在数组的1位置,其他位以此类推。这样从低位向高位摆放的原因是,便于进位,否则进位到新位时要移动n次。由于加减乘除通常混合,所以加减乘除的数都采用这种方法进行存储。
每一位数字 和
数组A的数字 , 数组B的数字 和 进位 的关系如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include<vector> #include<iostream> using namespace std; vector<int> add(vector<int> & A,vector<int> &B){ if(A.size()< B.size())return add(B,A); vector<int> 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(1); return C; } int main(){ vector<int> A,B; string 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; }
## 减法
借位
,结果数组C的数字 ,被减数的数字 和 减数的数字 的关系如下:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include<iostream> #include<vector> using namespace std; 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((10 + t) % 10); if(t < 0) t = 1; else t = 0; } while(C.size() > 1 && C.back() == 0) C.pop_back(); return C; } // 判断 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; } int main(){ vector<int> A,B; string 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'); 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; }
## 乘法
将一个高精度数A乘以另一个较小数b,方法是将高精度数的每一位乘以b,然后进位,类似加法。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include<iostream> #include<vector> using namespace std; vector<int> mult(vector<int> &A, int b){ vector<int> C; for(int i=0,t=0; i<A.size() || t>0; i++) { if(i < A.size()) t += b * A[i]; C.push_back(t % 10); t /= 10; } while(C.size() > 1 && C.back() == 0) C.pop_back(); return C; } int main(){ vector<int> A; string a; int b; cin >> a >> b; for(int i=a.size()-1; i>=0; i--)A.push_back(a[i]-'0'); auto C = mult(A,b); for(int i=C.size()-1; i>=0; i--)printf("%d",C[i]); return 0; }
除法
将一个高精度数A除以另一个较小数b,方法是将高精度数从最高位开始,取第一位,除以b,得到的商写入结果数组C。将刚刚得到余数乘以10加上第二位,将得到的和除以b…
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> div(vector<int> &A, int b,int &r){ vector<int> C; 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(){ vector<int> A; string a; int b; cin >> a >> b; for(int i=a.size()-1; i>=0; i--)A.push_back(a[i]-'0'); int r=0; 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; }
# 前缀和差分 ## 前缀和
将前数组中前j(j=1,2,…,n)位数字相加,得到前缀和。然后可以算任意区间内的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include<iostream> using namespace std; const int N = 1e5 + 10; int sum[N]; int main(){ int m,n; cin >> n >> m; int num; for(int i=0; i<n; i++) { cin >> num; sum[i+1] = sum[i] + num; } int l,r; while(m--){ cin >> l >> r; cout << sum[r] - sum[l-1] << endl; } }
## 子矩阵的和
和前缀和类似,算出每个以第一个节点为顶点的子矩阵的和sum[i,j]。然后算出以指定定点的子矩阵的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include<iostream> using namespace std; const int N = 1000 + 10; int sum[N][N]; int main(){ int n,m,q; cin >> n >> m >> q; int x; for(int i= 1; i<=n; i++) for(int j = 1; j<=m; j++) { cin >> x; sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1] + x; } int x1,y1,x2,y2; while(q--) { cin >> x1 >> y1 >> x2 >> y2; cout << (sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]) << endl; } return 0; }
## 差分
差分是前缀和的逆运算。a为原数组,同时也是前缀和,b为差分数组。先把原数组还原为差分的数组,即令b[i]
+= a[i] ,b[i+1] -= a[i]。然后再输入q个操作,在l上+m,在r+1减m。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include<iostream> using namespace std; const int N = 100010; int a [N],b[N]; void insert(int l,int r,int i){ b[l] += i; b[r+1] -= i; } int main(){ int n,m; cin >> n >> m; int x; for(int i = 1;i<=n; i++){ cin >> x;insert(i,i,x);} int l,r,c; for(int i=1; i<=m; i++){cin >> l >> r >> c;insert(l,r,c);} for(int i=1; i<=n; i++){a[i] = a[i-1] + b[i]; cout << a[i] << ' ';} return 0; }
差分矩阵
与一维差分类似。差分矩阵,也是用这种方法,根据子矩阵的和、差分也可以得出结论。a为前缀和矩阵,b为差分数组。把原始数组差分到b中,然后在b上进行q个操作,再对b前缀和得到结果a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include<iostream> using namespace std; const int N = 1010; int m,n,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(){ cin >> n>> m >> q; for(int i=1; i<=n; i++) for(int j=1; j<=m; j++){ cin >> a[i][j]; } // 将原有数据当做是后来插入的,面积为1 for(int i=1; i<=n ; i++) for(int j=1; j<= m; j++) insert(i,j,i,j,a[i][j]); int x1,y1,x2,y2,c; while(q--){ 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++) a[i][j] = b[i][j] -a[i-1][j-1]+a[i-1][j] + a[i][j-1]; for(int i=1; i<=n; i++) { for(int j=1; j<=m ; j++) cout << a[i][j] << ' '; cout << endl; } return 0; }
# 双指针算法
第一种在两个指针分别在两个序列中,第二种两个指针均在一个序列中。
模版
1 2 3 4 5 6 for(int i = 0,j=0; i < n; i++) { while(j<i && check(i,j)) j++; // 每道题的具体逻辑 }
核心思想 优化普通算法O(n^2)
为O(n)。
连续最长不重复子序列
先思考暴力解法,然后换成双指针算法。 发现j始终小于i,且j不会回退。
朴素做法
1 2 3 4 5 6 7 for (int i=0; i< n; i++){ for(int j=0; j<=i; j++){ if(check(j,i)) res = max(res,j-i+1); } }
双指针算法
1 2 3 4 for(int i=0,j=0; i< n; i++){ while(j<=i && check(j,i)) j++; res = max(res,j-i+1); }
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include<iostream> using namespace std; const int N = 100010; int a[N],s[N]; int main(){ int n; 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; }
数组元素目标和
类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include<iostream> using namespace std; const int N = 1e5 + 10; int a[N],b[N]; int main(){ int i,j; int n,m,x; cin >> n >> m >> x; for(i=0; i<n; i++) cin >> a[i]; for(i=0; i<m; i++) cin >> b[i]; for(i=0,j=m-1; i<n ; i++) { while(a[i] + b[j] > x ) { j--; } if(a[i] + b[j] == x) cout << i << ' '<< j-- << endl; } return 0; }
## 判断子序列 思想相同,代码也是很简洁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include<iostream> using namespace std; const int N = 1e5 + 10; int A[N],B[N]; int main(){ int n,m,i,j; cin >> n >> m; for(i = 0; i<n; i++) cin >> A[i]; for(i = 0; i<m; i++) cin >> B[i]; i = 0,j = 0; while(i < n && j <m ){ if(A[i] == B[j]) i++; j++; } if(i == n) cout << "Yes" << endl; else cout << "No" << endl; }
# 位运算 二进制第k位数字:
二进制返回n的最后一位数字1 : lowbit(n) = n & (~n + 1)
## 二进制中1的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include<iostream> using namespace std; int lowbit(int n){ return n & (~n+1); } int main(){ int n; cin >> n; // 暴力 /* int x; while(n--){ int y = 0; cin >> x; while(x){ y += x % 2; x /= 2; } cout << y << ' '; } */ // lowbit while(n--){ int x; cin >> x; int res=0; while(x) x -= lowbit(x),res ++; cout << res << ' ' ; } return 0; }
# 离散化
解释:https://zh.wikipedia.org/wiki/%E7%A6%BB%E6%95%A3%E5%8C%96
适用于值域跨度很大,但是用到的值很少。
## 区间和 这道题综合性强。用到二分,前缀和,离散化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include<iostream> #include<vector> #include<algorithm> using namespace std; const int N = 3e5 + 10; typedef pair<int,int> PII; int a[N],b[N]; vector<int> alls; vector<PII> adds,query; // 查找离散化后的坐标 int find(int x){ int l=0,r = alls.size()-1; while(l < r){ int mid = l+r >> 1; if(x > alls[mid]) l = mid+1; else r = mid ; } return r+1; } // int main(){ int n,m; cin >> n >> m; for(int i=0; i<n; i++){ int x,v; cin >> x >> v; alls.push_back(x); adds.push_back({x,v}); } 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(int i=0; i<n; i++){ int x = find(adds[i].first); a[x] += adds[i].second; } //前缀和 for(int i=1; i<=alls.size(); i++){ b[i] = b[i-1] + a[i]; } //数字和 for(int i=0; i<m; i++){ int l = find(query[i].first); int r = find(query[i].second); cout << b[r] - b[l-1] << endl; } return 0; }
#
贪心算法
贪心算法(英语:greedy
algorithm),又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。https://zh.wikipedia.org/wiki/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95
## 区间选点
两个区间没有交集时需要加两个点,否则只用一个,也可能出现多个区间公用一个点。先通过右端点进行排序(也可以左端点),然后如果l_{i+1}
< r_{i} 就不用加点,否则需要加点。
证明:设最终结果为ans,上述的结果为cnt。 由于结果为ans,所以 ans <=
cnt;
根据cnt的定义,有cnt个互无交集的区间,所以至少有答案至少有cnt个点,所以ans
>= cnt; 综上,ans==cnt;
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include<iostream> #include<algorithm> using namespace std; const int N = 1e5+10; struct Range{ int l,r; bool operator < (const Range &r) const{ return this->r < r.r; } }range[N]; int main(){ // cout << "aaaaaaaaa" << endl; int n; cin >> n; for(int i=0; i<n; i++) cin >> range[i].l >> range[i].r; sort(range,range+n); int min = -1e9 - 10; int res=0; for(int i=0; i<n; i++){ if(range[i].l > min){ res++; min = range[i].r; } } cout << res << endl; return 0; }