oi 基础算法/模板 小结

文章的各个算法的代码实现由本人码出,另外会有推荐大佬的相关博客
大概会涉及到:

  1. 树状数组
  2. 二分
  3. 快速排序
  4. 素数
  5. 并查集
  6. 最小生成树
  7. KMP
  8. 单调队列
  9. 链式前向星
  10. 单源最短路
  11. 最近公共祖先
  12. 区间最值查询

我这里大胆地用万能头文件来省略一大堆头文件
#include<bits/stdc++.h> 
using namespace std;

Binary Indexed Trees

关于树状数组博客
为什么只有树状数组,因为我不会线段树 【\笑哭】
我写的:https://www.cnblogs.com/callmebg/p/7228491.html
树状数组的原理和实现:https://www.cnblogs.com/George1994/p/7710886.html

#define lowbit(i) (i & -i) //宏定义lowbit,含义博客有
const int N = 500005;	  //一般把数据范围先写出来
int n, m;				   //n为输入的数组大小,m为查询次数
int bit[N];				   //bit[i]表示以i结尾长为lowbit(i)的数组和

void add(int x, int v) //将第x个数字加上v
{
	bit[x] += v; //每个含有原第x个数字的都要加v
	while (x <= n) //持续更新
	{		
		x += lowbit(x);	   //跳到下一个含有第x个数字的地方
	}
}
int query(int x) //查询前x个数的和
{
	int ans = 0;  //答案
	while (x > 0) //边界条件
	{
		ans += bit[x];  //加上该点的值
		x -= lowbit(x); //跳到下一个,还未加的地方
	}
	return ans;
}

Binary
关于二分的博客
你真的会写二分查找吗?:https://www.cnblogs.com/bofengyu/p/6761389.html
在从小到大的数组a的[x,y)范围内查找v的位置

int bsearch(int *a, int x, int y, int v)
{
	int mid;
	while (x < y)
	{
		mid = x + (y - x) / 2; //取中值
		if (v == a[mid])
			return mid; //刚好取到
		else if (v < a[mid])
			y = mid; //调整边界
		else
			x = mid + 1;
	}
	return -1; //无解
}

//下面代码是我自己写的lower_bound,可能有错
//在从小到大的数组a的[x,y]范围内返回第一个大于等于v的位置
int l_bound(int *a, int x, int y, int v)
{
	int mid;
	while (x < y)
	{
		mid = x + (y - x) / 2;
		if (a[mid] < v)
			x = mid + 1;
		else
			y = mid;
	}
	return x;
}

quick_sort
快排,当然,建议用sort
快排的递归和非递归版本:https://blog.csdn.net/Hadas_Wang/article/details/50917058
快排原理讲解:https://blog.csdn.net/TesuZer/article/details/80969006

void Qsort(int *p, int l, int r)
{
	int i = l;
	int j = r;
	int t;
	int mid = p[(l + r) / 2];
	while (i <= j)
	{
		while (p[i] < mid)	//从左端开始找不比“中间值”小的数的位置
			i++;
		while (p[j] > mid)	//从右端开始找不比“中间值”大的数的位置
			j--;
		if (i <= j)	//交换两值
		{
			t = p[i];
			p[i] = p[j];
			p[j] = t;
			i++;
			j--;
		}
	}
	//将剩下的两部分排序
	if (j > l)
		Qsort(p, l, j);
	if (i < r)
		Qsort(p, i, r);
}

素数
线性筛,用于线性时间筛选一定范围内的素数

线性筛法求素数:https://www.cnblogs.com/grubbyskyer/p/3852421.html

//显然MAX为数据范围的最大值
bool vis[MAX];		 //true为非素数,false为素数,开始时默认都为素数
int prime[MAX], cnt; //prime[i]表示第i个素数,cnt表示现在筛选出多少素数
void m_prime(int n)  //n表示要从1筛到n
{
	int i, j;
	cnt = 0;				//现在没有筛出素数
	vis[1] = true;			//1不是素数
	for (i = 2; i < n; i++) //从2开始,不用到n,因为如果n是合数,必然在之前被判断出来
	{
		if (!vis[i])
			prime[++cnt] = i;							//如果i还没有被访问到,则i是素数,存起来
		for (j = 1; j <= cnt && i * prime[j] <= n; j++) //j表示用每个素数去筛,i*prime[j]<=n防止筛过界
		{
			vis[i * prime[j]] = true; //显然i*prime[j]不是素数
			if (i % prime[j] == 0)
				break; //我忘了有什么用,大概是防止重复筛选吧
		}
	}
}

判断一个数是不是素数

#include <stdio.h>
#include <math.h>
int main()
{
	int m; // 输入的整数
	int i; // 循环次数
	int k; // m 的平方根
	printf("输入一个整数:");
	scanf("%d", &m);
	// 求平方根,注意sqrt()的参数为 double 类型,这里要强制转换m的类型
	k = (int)sqrt((double)m);
	for (i = 2; i <= k; i++)
		if (m % i == 0)
			break;
	// 如果完成所有循环,那么m为素数
	// 注意最后一次循环,会执行i++,此时 i=k+1,所以有i>k
	if (i > k)
		printf("%d是素数。\n", m);
	else
		printf("%d不是素数。\n", m);
	return 0;
}

//未完待续 			made by wcb
//2018年9月15日下午

并查集
百科:https://baike.baidu.com/item/并查集/9388442?fr=aladdin
并查集:https://blog.csdn.net/qq_32595453/article/details/80572191

int fa[N];		 //fa[i]表示i的祖先
void init(int n) //初始化
{
	for (int i = 1; i <= n; i++)
		fa[i] = i; //默认自己是自己的祖先
}
int getf(int x) //获取x的祖先
{
	return fa[x] = fa[x] == x ? x : getf(fa[x]);
	//路径压缩
}
void mer(int x, int y) //合并x和y
{
	if (getf(x) != getf(y)) //显然不能成环
	{
		fa[getf(x)] = fa[y]; //注意不能直接用fa【x】替换
	}
}

并查集应用
//Kruskal 最小生成树
https://blog.csdn.net/osc_2016_4/article/details/58590471
//涉及贪心 并查集

struct edge
{
	int f, t, d; //起点,终点,距离
} ed[M];
int n, m, ans = 0, now = 0; //n个点,m条边,答案,现在添加了几条边
int f[N];					//并查集
int getf(int t) return f[t] = f[t] == t ? t : getf(f[t]);
bool com(edge a, edge b) return a.d < b.d; //从小到大排序
void Kruskal()
{
	int i;
	for (i = 1; i <= n; i++)
		f[i] = i;
	sort(ed + 1, ed + m + 1, com);
	for (i = 1; i <= m; i++) //遍历边
	{
		if (getf(ed[i].f) != getf(ed[i].t)) //不能成环
		{
			now++;						   //添加一条边
			f[getf(ed[i].f)] = f[ed[i].t]; //把两点连接
			ans += ed[i].d;				   //加上该边距离
		}
		if (now == n - 1)
			break; //当添加到n-1条边就结束
	}
}

KMP
//KMP(不考)
KMP算法最浅显理解——一看就明白:https://blog.csdn.net/starstar1992/article/details/54913261

char s[MAX], t[MAX]; //s问题,t模板
int sl, tl, next[MAX];
void kmp()
{
	int i, j;
	for (i = 2, j = 0; i <= tl; i++)
	{
		while (j && t[i] != t[j + 1])
			j = next[j];
		//找到最长的前后缀重叠长度
		if (t[i] == t[j + 1])
			j++;
		next[i] = j;
	}
	for (i = 1, j = 0; i <= sl; i++)
	{
		while (j && s[i] != t[j + 1])
			j = next[j];
		//如果不匹配,则将利用kmp数组往回跳
		if (s[i] == t[j + 1])
			j++;
		if (j == tl)
		{
			printf("%d\n", i - tl + 1);
			j = next[j];
		}
	}
}

单调队列
//Monotone queue 滑动窗口
FZU 1894 志愿者选拔【单调队列】:https://blog.csdn.net/mengxiang000000/article/details/51207939
monotone queue(单调队列):https://blog.csdn.net/Dylan_Frank/article/details/52969191

struct node
{
	int data, id;		//数据,位置
} change;				//当前压入
node que[N];			//单调队列
int head = 0, tail = 0; //尾为不可访问,留给下次压入的位置
int n, m, a[N];			//n整个长度,m窗口
int i;
for (i = 1; i <= n; i++)
	scanf("%d", &a[i]);
for (i = 1; i <= n; i++)
{
	while (head < tail && a[i] <= que[tail - 1].data)
		tail--; //压入
	change.id = i, change.data = a[i];
	que[tail++] = change;
	if (que[head].id <= i - m)
		head++; //过期
	if (i >= m)
		printf("%d ", que[head.data]);
	//压入超过m才输出
}

链式前向星
用于存储图的边
【链式前向星+存图】讲解:https://blog.csdn.net/LOOKQAQ/article/details/81304637

int en = 0; //现在有多少条边
struct edge
{
	int t, v;   //终点,距离
	edge *next; //下一个相同起点的边的指针 (其实是上一个)
} * h[N], ed[M];
//h【i】表示以i为起点的边的指针
//ed【i】表示第i条边的终点,和下一个相同起点的边的指针
void add(int x, int y, int v) //添加一条x到y的边,长度为v
{
	ed[++en].next = h[x]; //重点,画图理解
	ed[en].t = y;
	ed[en].v = v;
	h[x] = ed + en; //重新拿到以x为起点的边集的指针
					//ed+en是ed[en]的指针
}

spfa
求解单源最短路径问题
最短路径问题—SPFA算法详解:https://blog.csdn.net/qq_35644234/article/details/61614581

void spfa(int s)
{
	int dist[N];					  //dist[i]表示起点到i的距离
	bool inque[N];					  //inque[i]表示i在队列与否
	queue<int> que;					  //一个队列,头文件queue
	memset(dist, 0x3f, sizeof(dist)); //初始化距离无穷大
	dist[s] = 0;					  //起点自身距离为0
	que.push(s);					  //将起点加入队列
	inque[s] = true;				  //起点在队列里

	while (!que.empty()) //当队列不为空
	{
		int now = que.front(); //取队列的头
		que.pop();			   //把头去掉
		inque[now] = false;	//原来的头不在队列里面了
		for (edge *e = v[now]; e; e = e->next)
		{
			//遍历以now为起点的边
			if (dist[e->e] > dist[now] + e->d)
			{
				//如果可以更新就更新
				dist[e->e] = dist[now] + e->d;
				if (!inque[e->e])
				{
					//如果该边终点不在队列里,则添加进去
					que.push(e->e);
					inque[e->e] = true;
				}
			}
		}
	}
}

//未完待续 			made by wcb
//2018年9月16日12:22:58

lca
//LCA倍增算法
最近公共祖先 LCA 倍增算法:https://www.cnblogs.com/FuTaimeng/p/5655616.html

int en = 0, n;
//链式前向星
struct edge
{
	int t;
	edge *next;
} * h[N], ed[M];
void add_edge(int s, int t)
{
	ed[++en].next = h[s];
	h[s] = ed + en;
	h[s]->t = t;
}

int deep[N];			   //deep[i]表示i的高度
int f[N][25];			   //f[i][j]表示i的2的j次方祖先
bool used[N];			   //防止重复搜
void dfs(int now, int dep) //一般先搜树的根,根深度为1
{
	used[now] = true; //标记
	deep[now] = dep;  //深度
	for (int k = 1; k <= 22; k++)
	{
		//神奇的递推
		int j = f[now][k - 1];
		f[now][k] = f[j][k - 1];
	}
	for (edge *e = h[now]; e; e = e->next)
	{
		//搜完now的儿子
		if (!used[e->t]) //防止重复搜索
		{
			f[e->t][0] = now;   //可以确定它爸爸是谁
			dfs(e->t, dep + 1); //接着dfs
		}
	}
	used[now] = false; //dfs的惯例,去除标记
}
int jump(int u, int step) //返回u的第step个祖先
{
	for (int k = 0; k <= 22; k++)
		if (step & (1 << k)) //二进制加速的跳
			u = f[u][k];
	return u;
}
int qlca(int u, int v)
{
	if (deep[u] < deep[v])
		swap(u, v);					//让u更深
	u = jump(u, deep[u] - deep[v]); //让u和v相同深度
	if (u == v)						//如果u往上跳,刚好到v
		return v;					//说明v就是他两的lca
	for (int k = 22; k >= 0; k--)
	{
		//神奇的操作,能保证跳到答案的儿子
		if (f[u][k] != f[v][k])
		{
			u = f[u][k];
			v = f[v][k];
		}
	}
	return f[v][0];
}

RMQ
区间最值查询
//RMQ的ST表模板
【模板】RMQ问题的ST表实现:https://www.cnblogs.com/YSFAC/p/7189571.html
RMQ:https://blog.csdn.net/qq_31759205/article/details/75008659

int st[N][20]; //st[i, j]表示从第i个数起连续2^j个数中的最值。
void rmq_init()
{
	int i, j;
	scanf("%d%d", &n, &m); //全文一样,n表示一般题目中的n,m表示查询数
	for (i = 1; i <= n; i++)
		scanf("%d", &st[i][0]);					//显然st[i][0]就是本身
	for (j = 1; (1 << j) <= n; j++)				//枚举长度,不能超过n
		for (i = 1; i + (1 << j) - 1 <= n; i++) //枚举起点,长度不超过n
			st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
	//st[i][j]的答案从以上两个答案中选出
}
int rmq(int l, int r)
{
	int k = 0;
	while ((1 << (k + 1)) < r - l + 1) //确保找到2的k次方能大于等于长度的一半
		k++;						   //因为得覆盖整个区域
	return min(st[l][k], st[r - (1 << K) + 1][k]);
}

三个算法,一篇解决!(图的最短路问题)
https://www.cnblogs.com/godfray/p/4077146.html

dijkstra的堆优化(我忘了)
https://blog.csdn.net/mu399/article/details/50903876

bellman-ford
https://blog.csdn.net/sms0101/article/details/73088422

floyd
http://developer.51cto.com/art/201403/433874.htm

发布了45 篇原创文章 · 获赞 14 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_34438779/article/details/88557886
OI