各种算法小结
预处理
预处理
-
快读:
-
字符串:
并查集
并查集
并查集支持两种操作:合并(合并两个不相交的集合)和查询(查询元素的根节点)。
并查集是一个树状结构。
- 初始化:
int fa[MAXN];
void init()
{
for(int i=1; i<=n; ++i)
fa[i]=i;
}
每一个节点的 father 设为自己。
- 查询:
int find(int x)
{
if(fa[x]==x)
return x;
else
return find(fa[x]);
}
查询节点的根节点。
通常可以加入路径压缩,将节点的父节点直接设为根节点,以优化时间。
else
{
fa[x]=find(fa[x]);
return fa[x];
}
- 合并:
void merge(int i, int j)
{
fa[find(i)]=find(j);
}
将不同集合的元素合并。
通常将小树往大树上合并,这样深度更小,也就是按秩合并。
void init()
{
for(int i=1; i<=n; ++i)
{
fa[i]=i;
rank[i]=1;
}
}
void merge(int i, int j)
{
int x=find(i), y=find(j);
if(rank[x] <= rank[y])
fa[x]=y;
else
fa[y]=x;
if(rank[x] == rank[y] && x != y)
rank[y]++;
}
注意比较的是根节点的秩。合并的两棵树如果深度相同,新的根节点深度要 +1。
https://www.luogu.com.cn/problem/P3367 (模板)
点击查看代码
#include<iostream>
using namespace std;
#define MAX 200000
int N,M;
int fa[MAX], ra[MAX];
void init()
{
for (int i = 0; i < MAX; ++i)
{
fa[i] = i;
ra[i] = 1;
}
}
int find(int x)
{
if (x == fa[x])
return x;
else
{
fa[x] = find(fa[x]);
return fa[x];
}
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if (a != b)
{
if (ra[a] < ra[b])
fa[a] = b;
else if (ra[a] > ra[b])
fa[b] = a;
else
{
fa[a] = b;
++ra[b];
}
}
}
bool query(int x, int y)
{
if (find(x) == find(y))
return true;
else
return false;
}
int main()
{
cin >> N >> M;
int Z, X, Y;
init();
while (M--)
{
cin >> Z >> X >> Y;
if (Z == 1)
{
merge(X, Y);
}
else if (Z == 2)
{
if (query(X, Y))
cout << "Y" << endl;
else
cout << "N" << endl;
}
}
return 0;
}
https://www.luogu.com.cn/problem/P1551 (亲戚)
点击查看代码
#include<iostream>
using namespace std;
#define MAX 10000
int n, m, p;
int fa[MAX], ra[MAX];
void init()
{
for (int i = 0; i < n; ++i)
{
fa[i] = i;
ra[i] = 1;
}
}
int find(int x)
{
if (x == fa[x])
return x;
else
{
fa[x] = find(fa[x]);
return fa[x];
}
}
bool query(int x,int y)
{
if (find(x) == find(y))
return true;
else
return false;
}
void merge(int x,int y)
{
int a = find(x), b = find(y);
if (a!=b)
{
if (ra[a] < ra[b])
fa[a] = b;
else if (ra[a] > ra[b])
fa[b] = a;
else
{
fa[a] = b;
++ra[b];
}
}
}
int main()
{
int x,y;
cin >> n >> m >> p;
init();
while (m--)
{
cin >> x >> y;
merge(x-1, y-1);
}
while (p--)
{
cin >> x >> y;
if (query(x-1, y-1))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
https://www.luogu.com.cn/problem/P3958 (奶酪)
点击查看代码
#include <iostream>
#include <cmath>
using namespace std;
#define MAX 100000
typedef long long ll;
int T, n;
int key[MAX], fa[MAX], ra[MAX];
ll h, r;
ll X[MAX], Y[MAX], Z[MAX], height[MAX];
double dist(int i, int j)
{
return sqrt((X[i] - X[j]) * (X[i] - X[j]) + (Y[i] - Y[j]) * (Y[i] - Y[j]) + (Z[i] - Z[j]) * (Z[i] - Z[j]));
}
bool check(int i, int j)
{
double d = dist(i, j);
return d <= r * 2.0;
}
void init()
{
for (int i = 0; i < n; ++i)
{
fa[i] = i;
ra[i] = 1;
key[i] = 0;
}
}
int find(int x)
{
if (x == fa[x])
return x;
else
{
fa[x] = find(fa[x]);
return fa[x];
}
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if (a != b)
{
if (ra[a] < ra[b])
{
height[b] = max(height[a], height[b]);
fa[a] = b;
}
else if (ra[a] > ra[b])
{
height[a] = max(height[a], height[b]);
fa[b] = a;
}
else
{
height[b] = max(height[a], height[b]);
fa[a] = b;
++ra[b];
}
}
}
int main() {
cin >> T;
while (T--)
{
cin >> n >> h >> r;
init();
for (int i = 0; i < n; ++i)
{
cin >> X[i] >> Y[i] >> Z[i];
height[i] = Z[i] + r;
}
for (int i = 0; i < n; ++i)
{
for (int j = i + 1; j < n; ++j)
{
if (check(i, j))
merge(i, j);
}
}
for (int i = 0; i < n; ++i)
{
if (Z[i] <= r && Z[i] > -r)
key[find(i)] = 1;
}
bool flag = false;
for (int i = 0; i < n; ++i) {
if (height[i] >= h && key[find(i)])
{
flag = true;
break;
}
}
cout << (flag ? "Yes" : "No") << endl;
}
return 0;
}
树状数组
树状数组
树状数组支持两个操作:单点修改(更改数组中一个元素的值)和区间查询(查询一个区间内所有元素的和)。
顾名思义,树状数组是一棵树。
如图,c1 存储位置 1 的和(值),c4 存储位置 1,2,3,4 的和,c6 存储位置 5,6 的和。
引入 lowbit 函数:
lowbit(x)=(x)&(-x)
这返回该数最低位的 1 及之后的所有 0,如 lowbit(101000)=1000。
- 单点修改:
void update(int i,int x)
{
for(int pos=i;pos<MAXN;pos+=lowbit(pos))
tree[pos]+=x;
}
当需要修改位置 3 的时候,实际上只需要修改 c3(011) -> c4(100) -> c8(1000),是一个爬树过程。
- 求前缀和:
int query(int n)
{
int ans=0;
for(int pos=n;pos;pos-=lowbit(pos))
ans+=tree[pos];
return ans;
}
本质上相当于 101001=101000+1=100000+1000+1,从右向左依次计算。
- 区间查询:
int query(int a,int b)
{
return query(b)-query(a-1);
}
https://www.luogu.com.cn/problem/P3374 (模板)
点击查看代码
#include<iostream>
using namespace std;
#define MAX 500000
int n, m;
int tree[MAX];
int lowbit(int x)
{
return x & (-x);
}
void update(int x, int y)
{
for (int i = x; i <= n; i += lowbit(i))
tree[i] += y;
}
int prefix(int x)
{
int sum = 0;
for (int i = x; i; i -= lowbit(i))
sum += tree[i];
return sum;
}
int query(int x, int y)
{
return prefix(y) - prefix(x-1);
}
int main()
{
int x, y, z, t;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
cin >> t;
update(i, t);
}
while (m--)
{
cin >> x >> y >> z;
if (x == 1)
{
update(y, z);
}
else if (x == 2)
{
cout << query(y, z) << endl;
}
}
return 0;
}
存图
存图
常见的存图方法有:邻接矩阵,邻接表,链式前向星。
- 邻接矩阵:
void add(int u,int v,int w)
{
mat[u][v]=w;
mat[v][u]=w;
}
通常这种方法最多只能处理几千个点。
- 邻接表:
void add(int from,int to, int w)
{
Edge e={to,w};
edges[from].push_back(e);
}
每个 from 即为起点,将邻边依次 push_back。
- 链式前向星:
链式前向星是邻接表的变种,但是把不同边的连接顺序反过来了:
void add(int from,int to,int w)
{
edges[++cnt].w=w;
edges[cnt].to=to;
edges[cnt].next=head[from];
head[from]=cnt;
}
其中 cnt 记录边的序号,每一个新加入的边都采用头插法。
快速幂
快速幂
- 递归:
int qpow(int a,int n)
{
if(n==0)
return 1;
else if(n%2==1)
return qpow(a,n-1)*a;
else
{
int temp=qpow(a,n/2);
return temp*temp;
}
}
这是一个二分法的过程。
- 非递归:
int qpow(int a,int n)
{
int ans=1;
while(n)
{
if(n&1)
ans*=a;
a*=a;
n>>1;
}
return ans;
}
这是一种类似 x^(1010) = x^(1000) + x^(10) 的处理方法,任何一个幂 x^p 都可以写成 x^1 ,x^2 ,x^4 等相加。
https://www.luogu.com.cn/problem/P1226 (模板)
点击查看代码
#include <iostream>
using namespace std;
typedef unsigned long long ull;
ull a, b, p;
ull foo1(ull x, ull y, ull p)
{
x = x % p;
if (y == 0)
return 1;
else if (y % 2 == 1)
{
return foo1(x, y - 1, p) * x;
}
else
{
ull temp = foo1(x, y / 2, p) % p;
return temp * temp % p;
}
}
ull foo2(ull x, ull y, ull p)
{
ull res = 1;
x = x % p;
while (y)
{
if (y % 2)
res = (res * x) % p;
x = (x * x) % p;
y = y / 2;
}
return res;
}
int main()
{
cin >> a >> b >> p;
cout << a << "^" << b << " mod " << p << "=" << foo1(a, b, p) % p;
return 0;
}
高精度
高精度
- 高精度加法:
c[i] = a[i] + b[i];
if (c[i] >= 10)
{
c[i] -= 10;
c[i + 1]++;
}
- 高精度减法:
if (a[i] < b[i])
{
a[i + 1]--;
a[i] += 10;
}
c[i] = a[i] - b[i];
- 高精度乘法:
c[i + j] += a[i] * b[j] + x;
x = c[i + j] / 10;
c[i + j] %= 10;
- 高精度除法:
这里采用手动模拟除式运算。
for (int i = 0; i < la; ++i)
d[i] = a[i];
for (int i = la - lb; i >= 0; --i)
{
while (greater_eq(d, b, i, lb))
{
for (int j = 0; j < lb; ++j)
{
d[i + j] -= b[j];
if (d[i + j] < 0)
{
d[i + j + 1] -= 1;
d[i + j] += 10;
}
}
c[i] += 1;
}
}
搜索
- DFS:
https://www.luogu.com.cn/problem/P1605#submit (迷宫)
点击查看代码
#include<iostream>
using namespace std;
int n, m, t, sx, sy, fx, fy, ans, L[10][10], V[10][10];
int dir[4][2] = { {1,0},{0,1},{-1,0},{0,-1} };
bool check(int x, int y)
{
if (x >= 1 && x <= n && y >= 1 && y <= m && L[x][y] == 0 && V[x][y] == 0)
return true;
else
return false;
}
void DFS(int x,int y)
{
if (x == fx && y == fy)
{
++ans;
return;
}
int tx, ty;
for (int k = 0; k <= 3; ++k)
{
tx = x;
ty = y;
tx += dir[k][0];
ty += dir[k][1];
if (check(tx, ty))
{
V[tx][ty] = 1;
DFS(tx, ty);
V[tx][ty] = 0;
}
}
}
int main()
{
cin >> n >> m >> t >> sx >> sy >> fx >> fy;
int tx, ty;
while (t--)
{
cin >> tx >> ty;
L[tx][ty] = 1;
}
V[sx][sy] = 1;
DFS(sx, sy);
cout << ans;
return 0;
}
- BFS:
最短路径
- Floyd:
O(n^3) 强行遍历. 注意是 O(n) 里套了一个 O(n^2), 如果写成 O(n^2) 里套 O(n) 是不可行的, 可以设想起点和终点之间有多个中间点的情况.
-
Bellman:
-
Dijkstra:
核心就是 "每次加入更新后最近的节点", 通过维护 dis[] 数组得到起点到其余各点的距离.
- A*:
核心是 f(x) = g(x) + h(x), 其中 g(x) 是到起点的距离, f(x) 是可到终点的估计距离 (启发式函数 heuristic), 每次选取最小的 f(x) 扩展节点. 但是在更新时, A* 算法仍然只根据 g(x) 判断. Dijkstra 算法就是没有 h(x) 的 A* 算法.
排序
-
快速排序:
-
堆排序:
-
希尔排序:
-
归并排序:

浙公网安备 33010602011771号