各种算法小结

预处理

预处理

  1. 快读:

  2. 字符串:

并查集

并查集

并查集支持两种操作:合并(合并两个不相交的集合)和查询(查询元素的根节点)。

并查集是一个树状结构。

  1. 初始化:
int fa[MAXN];
void init()
{
    for(int i=1; i<=n; ++i)
        fa[i]=i;
}

每一个节点的 father 设为自己。

  1. 查询:
int find(int x)
{
    if(fa[x]==x)
        return x;
    else
        return find(fa[x]);
}

查询节点的根节点。

通常可以加入路径压缩,将节点的父节点直接设为根节点,以优化时间。

    else
    {
        fa[x]=find(fa[x]);
        return fa[x];
    }
  1. 合并:
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。

  1. 单点修改:
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),是一个爬树过程。

  1. 求前缀和:
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,从右向左依次计算。

  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;
}
存图

存图

常见的存图方法有:邻接矩阵,邻接表,链式前向星。

  1. 邻接矩阵:
void add(int u,int v,int w)
{
    mat[u][v]=w;
    mat[v][u]=w;
}

通常这种方法最多只能处理几千个点。

  1. 邻接表:
void add(int from,int to, int w)
{
    Edge e={to,w};
    edges[from].push_back(e);
}

每个 from 即为起点,将邻边依次 push_back。

  1. 链式前向星:

链式前向星是邻接表的变种,但是把不同边的连接顺序反过来了:

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 记录边的序号,每一个新加入的边都采用头插法。

快速幂

快速幂

  1. 递归:
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;
    }
}

这是一个二分法的过程。

  1. 非递归:
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;
}
高精度

高精度

  1. 高精度加法:
c[i] = a[i] + b[i];
if (c[i] >= 10) 
{
    c[i] -= 10;
    c[i + 1]++;
}
  1. 高精度减法:
if (a[i] < b[i]) 
{
    a[i + 1]--;
    a[i] += 10;
}
c[i] = a[i] - b[i];
  1. 高精度乘法:
c[i + j] += a[i] * b[j] + x;
x = c[i + j] / 10;
c[i + j] %= 10;
  1. 高精度除法:

这里采用手动模拟除式运算。

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;
    }
}

搜索

  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;
}
  1. BFS:

最短路径

  1. Floyd:

O(n^3) 强行遍历. 注意是 O(n) 里套了一个 O(n^2), 如果写成 O(n^2) 里套 O(n) 是不可行的, 可以设想起点和终点之间有多个中间点的情况.

  1. Bellman:

  2. Dijkstra:

核心就是 "每次加入更新后最近的节点", 通过维护 dis[] 数组得到起点到其余各点的距离.

  1. A*:

核心是 f(x) = g(x) + h(x), 其中 g(x) 是到起点的距离, f(x) 是可到终点的估计距离 (启发式函数 heuristic), 每次选取最小的 f(x) 扩展节点. 但是在更新时, A* 算法仍然只根据 g(x) 判断. Dijkstra 算法就是没有 h(x) 的 A* 算法.

排序

  1. 快速排序:

  2. 堆排序:

  3. 希尔排序:

  4. 归并排序:

二分

动态规划

最小生成树

posted @ 2023-12-07 12:30  rainrzk  阅读(27)  评论(0)    收藏  举报