目录
1.排序
1.1.快速排序
1.2.归并排序
2.二分
2.1.整数
2.2.浮点数
3.高精度
3.1.高精度加法
3.2.高精度减法
3.3.高精度乘法
3.4.高精度除法
4.前缀和
5.差分
6.双指针算法
7.位运算
8.离散化
8.1.unique函数实现
9.区间合并
1.排序
1.1.快速排序
快速排序的基本思想来自于分治。
首先,确定分界点的方法:
-
q[left];
-
q[(left + right) / 2];
-
q[right];
-
随机
第二步,则是调整区间,按照相应的要求,将数值划分为两部分。
第三步,递归处理左右两段数值。
使用两个指针分别指向左右两端,判断左右两端与指定的分界值x之间的关系,做出相应的调整。
快速排序模板:
#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;
}
1.2.归并排序
也是通过分治的思想,将数组分成left和right
-
确定
-
递归排序left、right
-
归并——合二为一
编辑#include<iostream>
using namespace std;
#define N 10000
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;
}
vector容器的模板
void merge_sort(vector<int>& arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
merge_sort(arr, left, mid);
merge_sort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> left_arr(arr.begin() + left, arr.begin() + mid + 1);
vector<int> right_arr(arr.begin() + mid + 1, arr.begin() + right + 1);
int i = left - 1;
int j = mid;
int k = 0;
while (k < right - left + 1) {
if (j == right) {
arr[++i] = left_arr[++k];
} else if (left_arr[++k] <= right_arr[j]) {
arr[++i] = left_arr[k];
} else {
arr[++i] = right_arr[j];
}
}
}
其中,merge_sort
函数是递归函数,将待排序的数组分为左右两部分,再分别调用merge_sort
函数进行排序。merge
函数则是将左右两个已排序的数组合并成一个有序数组。
2.二分
通过某一条性质,每一次从中间进行分割,一半满足条件,一般不满足。不满足的那一部分就直接毙掉,满足的那一部分在调用二分法进行框定界限,从而使确定值越来越接近。
2.1.整数
前提是要排好序的,且必须要有解。
整数二分法题目 - 数的范围:
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
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;
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;
if(q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
2.2.浮点数
开平方:
#include<iostream>
using namespace std;
int main()
{
double x;
scanf("%lf", &x);
double l = 0, r = x;
while(r - l > 1e-8)//经验值,比要求小数多两位
{
double mid = (l + r) / 2;
if(mid * mid >= x) r = mid;
else l = mid;
}
printf("%lf", l);
return 0;
}
3.高精度
3.1.高精度加法
两个正整数相加
#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;//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--)
{
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;
}
3.2.高精度减法
两个正整数相减。
首先要考虑到正负号,当小数减大数的时候,一定是负的;
其次是一位不够,向上借位。
#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;
int t = 0;
for(int i = 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.back();//去掉前导0
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');
}
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;
}
3.3.高精度乘法
两个正整数相乘,长整数乘上短整数的代码。
#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;
}
3.4.高精度除法
高精度的整数除以低精度的整数。
#include<iostream>
#include<vector>
#include<algorithm>
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) 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;
}
4.前缀和
Si = a1 + a2 + a3 + ..... + an (注意:S0 = 0)
如何求Si:
for(int i = 1;i <= n; i++) { S[i] = S[i-1] + a[i]; }
作用:[l, r] 时间复杂度O(n)
用一次运算来计算任意段内的值:Sr - Sl-1
#include<iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];
int main()
{
ios::sync_with_stdio(false);//对cin的判断
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];
}
while(m--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l-1]);
}
return 0;
}
图的面积裁剪问题
求和S[i] [j],这时候能用我们熟知的割补法来进行计算
S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1- 1][y1 -1];
这一块还是简单的,代码也是好写的
#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%d", &x1, &y1, &x2, &y2);
printf("%d", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1 -1]);//算部分矩阵和
}
return 0;
}
5.差分
是前缀和的逆运算
a1, a2, .... an 构造b1, b2, ... , bn
b1 =a1 b2 = a2 - a1 bn = an - an-1
a就是b的前缀和,b就是a的差分
差分题目:
输入一段长度为n的整数序列
接下来输入m个操作,每个操作包含三个整数l,r,c,表示将序列中[l,r]之间的每个数加上c,输出进行完所有操作后的序列。
#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;
}
我们上面的代码是一维的差分,接下来我们来构造一个差分矩阵。
b[x1][y1] += c; b[x2+1][y1] -= c; b[x1][y2+1] -= c; b[x2+1][y2+1] += c;
实现代码:
#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++)
{
insert(i, j, i, j, a[i][j]);
}
}
while(q--)
{
int x1, x2, y1, 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]);
}
}
return 0;
}
6.双指针算法
for(int i = 0, j = 0;i < n; i++) { while(j < i && check(i, j)) j++; //每道题目的具体思路 }
核心思想:
for(int i = 0;i < n; i++) { for(in j = 0;j < n; j++) } //算法复杂度O(n^2)
双指针算法能将上述的朴素算法优化到O(n)
输出字符串的每个单词:
前提每个单词间只有一个空格。
#include<iostream>
#include<string.h>
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;
}
return 0;
}
最长连续不重复子序列:
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续序列,输出它的长度。
//朴素算法
for(int i = 0;i < n; i++) { for(int j = 0;j <= i; j++) { if(check(j, i)) { res = max(res, i - j + 1); } } }
//双指针算法
for(int i = 0, j = 0;i < n; i++) { while(j <= i && check(j, i)) j++; res = max(res, i - j + 1); }
j:j往左最远能到的位置
题目代码模板:
#include<iostream>
using namespace std;
const int N = 100010;
int n;
int a[N], s[N];
int main()
{
scanf("%d", &n);
for(int i = 0;i < n; i++)
{
scanf("%d", &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);
}
printf("%d\n", res);
return 0;
}
7.位运算
n的二进制表示第k位
-
先把第k位数字移到最后一位 n >> k
-
再观察个位的数字 x & 1
-
lowbit(x):返回x的最后一位 x & -x = x & (~x + 1)
#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++;//每次减去x的最后一位
cout << res << ' ';
}
return 0;
}
数据在内存中以2进制的形式存储 对于整数来说: 整数二进制有3种表示形式:原码、反码、补码 正整数:原码、反码、补码相同 负整数:原码、反码、补码要进行计算的 按照数据的数值直接写出的二进制序列就是原码 原码的符号位不变,其他位按位取反,得到的就是反码 反码+1,得到的就是补码
存储举例1:
int a = -10;
1000 0000 0000 0000 0000 0000 0000 1010 - 原码
1111 1111 1111 1111 1111 1111 1111 0101 - 反码
1111 1111 1111 1111 1111 1111 1111 1110 - 补码
FFFFFFF6-内存存储
内存存补码。
#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 << ' ';
}
return 0;
}
8.离散化
假设有一串极大的数据,我们对这串数据a[i]进行离散化,其中a[i]中可能会有重复的元素,首先就是处理去重的问题,然后再进行一一映射。
vector<int>alls;//存储所有有待离散化的值 sort(alls.begin(), alls.end());//将所有值排序 alls.erase(unique(alls.begin(), alls.end()), alls.end());//去重 //二分法求出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; }
例题:区间和
假设有一个无限长的数轴,数轴上的每个坐标上的数都是0。
现在,我们首先进行n次操作,每次操作将某一位置x上的数+c
接下来,进行m次询问,每次询问包含两个整数l和r,你需要求出区间l和r之间所有数的和。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N];
vector<int> alls;
vector<PII> 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), r = find(item.second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}
8.1.unique函数实现
vector<int>::iterator unique(vector<int> &a) { int j = 0; for(int i = 0;i < a.size(); i++) { if(!a || a[i] != a[i - 1]) a[j++] = a[i]; } //a[0]~a[j-1]所有的数都是不同的 return a.begin() + j; }
排完序的内容中,数要么就是第一个,要么就是a[i] ≠a[i - 1],Java和python方向是没有unique函数的,如果要写的话,就是做一个这样的迭代器。
9.区间合并
假设有大量区间,有的区间之间有重叠的部分,我们要将有交集的区间进行合并。
-
按区间左端点排序
-
扫描整个区间,将所有可能有交点的区间进行合并
#include<iostream>
#include<vector>
#include<algorithm>
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;
}