第一讲-------基础算法
快速排序----分治
确定分界点 q[l],q[(l+r)/2],q[r],随机
调整区间 (三部分)
<= x x >=x
递归处理左右两端
快速排序算法模板
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);
}
归并排序----分治----双指针算法
1.确定分界点:以数组的中心点分l+r>>1;
2.递归排序左边和右边 使左右有序
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];
}
逆序对的数量(暴力,归并排序,树状数组)
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。。数据范围1≤n≤100000,数列中的元素的取值范围 [1,1e9]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], tmp[N];
LL merge_sort(int q[], int l, int r)
{
if (l >= r) return 0;
int mid = l + r >> 1;
LL res = 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
{
res += mid - i + 1;
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];
return res;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
cout << merge_sort(a, 0, n - 1) << endl;
return 0;
}
如果更新方式是r=mid,则mid=l+1>>1不用做任何处理,否则mid=l+r+1>>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;
}
浮点数二分算法模板
bool check(double x) {
/* ... */} // 检查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;
}
高精度加法
例题:给定两个正整数(不含前导 0),计算它们的和。数据范围1<=整数长度<=100000
输入样例:12 23 输出样例:35
代码:
不圧位代码
#include <iostream>
#include <vector>
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(t);
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');
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
压9位的代码
#include <iostream>
#include <vector>
using namespace std;
const int base = 1000000000;
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 % base);
t /= base;
}
if (t) C.push_back(t);
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
{
s += (a[i] - '0') * t;
j ++, t *= 10;
if (j == 9 || i == 0)
{
A.push_back(s);
s = j = 0;
t = 1;
}
}
for (int i = b.size() - 1, s = 0, j = 0, t = 1; i >= 0; i -- )
{
s += (b[i] - '0') * t;
j ++, t *= 10;
if (j == 9 || i == 0)
{
B.push_back(s);
s = j = 0;
t = 1;
}
}
auto C = add(A, B);
cout << C.back();
for (int i = C.size() - 2; i >= 0; i -- ) printf("%09d", C[i]);
cout << endl;
return 0;
}
高精度减法
给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。数据范围1≤整数长度≤1e5
输入样例:32 11 输出样例:21
#include <iostream>
#include <vector>
using namespace std;
vector<int> C;
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)
{
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;
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)) C = sub(A, B);
else C = sub(B, A), cout << '-';
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
高精度乘法
123*12=1476
把12看成整体,
(3*12)%10=6;
(2*12+(3*12)/10)%10=(2*12+3)%10=7;
(1*12+(2*12+3)/10)%10=14%10=4;
14/10=1;
例题:给定两个非负整数(不含前导 0) A 和 B,请你计算A×B 的值。1≤A的长度≤100000,0≤B≤10000
输入样例:2 3 输出样例:6
#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(); //*0的情况
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;
}
高精度除法
给定两个非负整数(不含前导 0) A,B,请你计算A/B 的商和余数,
数据范围1≤A的长度≤100000 ,1≤B≤10000,B 一定不为 0
输出格式:共两行,第一行输出所求的商,第二行输出所求余数。
输入样例:7 2
输出样例:3 1
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//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;
vector<int> A;
int B;
cin >> a >> B;
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 << endl;
return 0;
}
一维前缀和
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
二维前缀和
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]
一维差分
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
二维差分
给以(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
例题:输入一个 n 行 m列的整数矩阵,再输入 qq个操作,每个操作包含五个整数 x1,y1,x2,y2,c每个操作要将选中的子矩阵中的每个元素的值加上 c。请你将进行完所有操作后的矩阵输出。
输入格式:第一行包含整数 n,m,q。接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式:表示所有操作进行完毕后的最终矩阵。
数据范围1≤n,m≤1000,1≤q≤100000,1≤x1≤x2≤n,1≤y1≤y2≤m,−1000≤c≤1000,−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 <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, y1, x2, y2, c;
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 ++ )
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]);
puts("");
}
return 0;
}
双指针算法 —— 模板题 AcWIng 799. 最长连续不重复子序列, AcWing 800. 数组元素的目标和
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
例题:给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。数据范围1≤n≤1e5
输入样例:5
1 2 2 3 5
输出样例:3
代码:
#include <bits/stdc++.h>
using namespace std;
int const N=1e5+10;
int n;
int a[N],b[N];
int res,maxx;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0,j=0;i<n;i++)
{
b[a[i]]++;
while(j<i&&b[a[i]]>1)
b[a[j++]]--;
maxx=max(maxx,i-j+1);
}
cout<<maxx<<endl;
return 0;
}
位运算
^异或:相同为0,不同为1 I或:有一个为1值为1
&与:同为1值为1 ~取反:将0变1,将1变0
<<左移:将一个数的各二进制位全部左移N位,右补0;
.>>右移:将一个数的各二进制位全部右移N位,移到右端的低位被舍弃,对于无符号数,高位补0
X=1010
原码 0…01010
反码 1…10101
补码 1…10110
~x+1==-x
n的二进制表示中第k位是几
1.先把第k位移到最后一位
2.看各位数是几
n>>k&1
例如:输出-10的二进制 11......10110
#include<iostream>
using namespace std;
int main()
{
int n=10;
unsigned int x=-n;
for(int i=31;i>=0;i--)
cout<<(x>>i & 1)
return 0;
}
lowbit()函数 [树状数组的基本操作]
用来取一个二进制最低位的1与后面的0组成的数
例如 5(101) lowbit(5)=1 (1)
12(1100) lowbit(12)=4(100)
lowbit(x)
x&-x = x&(~x+1)
X=1010…100…0
~x=0101…011…1
~x+1=0101…100…0
x&(~x+1)=0000…100…0=100…0
应用:可以统计x里面的1的个数
例题
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。数据范围1≤n≤100000,
输入样例:5
1 2 3 4 5
输出样例:1 1 2 1 2
代码:#include<bits/stdc++.h>
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);//每次减去x的最后一位
res++; }
cout<<res<<" ";
}
return 0; }
二进制 十进制 [秦九韶算法]
1010 10
1010 1*2+0=2 (10)
1010 2*2+1=5 (101)
1010 5*2+0=10 (1010)
int get(string s,int b) //将b进制的数转化为十进制
{
int res=0;
for(auto c:s)
res+=res*b+(c-’0’);
return res;
}
离散化
满足离散化的性质:值域大,数稀疏
a[ ] : { 1 , 3 , 100 , 200 , 500000000 } 映射
下标:0 1 2 3 4
1.a[]中可能有重复的元素 去重
2.如何计算出离散化的值 二分(此题有序)
例题:区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n 次操作,每次操作将某一位置 x上的数加 c。接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。输入格式:第一行包含两个整数 n 和 m。接下来 n 行,每行包含两个整数 x 和 c。再接下来 m 行,每行包含两个整数 l 和 r。输出格式:共 m 行,每行输出一个询问中所求的区间内数字和。数据范围−1e9≤x≤1e9,1≤n,m≤1e5,−1e9≤l≤r≤1e9,−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
5
0
代码:#include<bits/stdc++.h>
using namespace std;
int n,m,l,r;
int const N=3e5+10; //数组里面存x,l,c对应离散化的值,个数最多3*1e5
int a[N],s[N];
vector<int> lisan; //存入所有离散化的值
typedef pair<int,int> PII;
vector<PII> addxc,xunwenlr; //插入x c; 插入询问l r;
int find(int x) //求离散化的结果
{
int l=0,r=lisan.size()-1;
while(l<r)
{
int mid=l+r >> 1;
if(lisan[mid]>=x) r=mid;
else l=mid+1;
}
return r+1; //坐标加1,映射到从1开始;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
int x,c;
cin>>x>>c;
lisan.push_back(x);
addxc.push_back({
x,c});
}
for(int i=0;i<m;i++)
{
cin>>l>>r;
xunwenlr.push_back({
l,r});
lisan.push_back(l);
lisan.push_back(r);
}
sort(lisan.begin(),lisan.end());
lisan.erase(unique(lisan.begin(),lisan.end()),lisan.end()); //去重,插入的x l r 去掉相同的
for(auto item:addxc) //插入处理
{
int xx=find(item.first); //xx离散化所对应的下标
a[xx]+=item.second;
}
for(int i=1;i<=lisan.size();i++) //前缀和
s[i]=s[i-1]+a[i];
for(auto item:xunwenlr) //处理询问
{
l=find(item.first); //离散化所对应的下标
r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
} return 0;}
//上面去重unique函数和erase函数 原代码
vector<int>::iterator unique <vector<int> &a)
{
int j=0;
for(int i=0;i<a.size();i++)
if( !i || a[i]!=a[i-1] )
a[j++]=a[i];
return a.begin()+j;
}
unique()函数,后面的元素替换的前面重复的元素,一般排序后使用(去除的是相邻元素),后面的空间并没有删除掉
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[]={
2,3,4,4,6};
sort(a,a+5); //一般在使用unique之前都需要进行排序;
unique(a,a+5); //使用unique函数对数组进行去重
for(int i=0;i<5;i++)
{
cout<<a[i]<<” “; // 2 3 4 6 6
}
cout<<"不重复数列的长度:"<<unique(a,a+5)-a<<endl; //不重复序列的长度:4
}
区间合并
// 将所有存在交集的区间合并
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;
}