蓝桥杯考常考点模板

一、约数

求一个数的所有约数

1、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

int n, a[maxl], cnt = 0;

int main(){
	cin >> n;
	for (int i = 1; i <= n/i; i++){//不是i * i <= n 原因: i * i可能超出整形范围 
		if (n % i == 0)	{
			a[cnt++] = i;
			if (i != n/i)	a[cnt++] = n/i;//这一行利用约数的性质,即可遍历一半的数求所有约数 
		}
	}
	for (int i = 0; i < cnt; i++)	cout << a[i] << " ";
    return 0;
}

2、应用

  • c/c++ b组 试题 D: 货物摆放
    • 先求约束,再求乘积(降低复杂度)

二、最大公约数与最小公倍数

1、流程

定义整数a、b,求其最大公约数与最小公倍数

  • 求最大公约数
  • 求最小公倍数(a*b = 最大公约数 * 最小公倍数)

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

int n, m, prime; 

int gcd(int a, int b){//辗转相除法——都求余数了一看就是求最大公约数的 
	return (b == 0 ? a : gcd(b, a % b));//因为最小公倍数,最小也要比max(n, m)大 
}

int main(){
	cin >> n >> m;
	prime = gcd(n, m);
	cout << n << "与" << m << "的最大公约数数为:" << prime << endl;
	cout << n << "与" << m  << "的最小公倍数为:" << m * n / prime << endl;
    return 0;
}

三、时间模板

1、时间单位转换

  • 毫秒、秒、分种、小时、天

    • 1天 = 24小时
    • 1小时 = 60分种
    • 1分钟 = 60秒
    • 1秒 = 1000毫秒
  • 1 ~ 12 月份天数:31、28、31、30、31、30、31、31、30、31、30、31

  • 其中润年29天(闰年——该年份能被4整除但是同时不能被100整除或者能被400整除的是润年)

2、用到的函数

#include<iostream>
int sprintf(char *str, const char *format, ...)/*格式化字符串*/

3、模板

  1. 天数模拟
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

int a[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};//月份1 3 5 7      8 10 12	这些月份31天其他除了2月都是30天 
int year = 2023, month = 4, day = 5, week = 3;//开始时间 
char ch[10];

int main(){
	while (1){
		if (year == 2025 && month == 1 && day == 1)	break;//结束时间 
		sprintf(ch, "%04d-%02d-%02d", year, month, day);//格式化字符串 
		for (int i = 0; i < int(strlen(ch)); i++)	cout << ch[i];
		cout << '\t' << "星期:" << week << endl;
		day++;
		week++;
		if (week > 7)	week = 1;
		if (((year % 4 == 0 && year % 100!= 0) || year % 400 == 0) && month == 2){//闰年&&是二月份 
			if (day > a[month] + 1){
				day = 1;
				month++;
			}
		}
		else if (day > a[month]){
			day = 1;
			month++;
		}
		if (month > 12){
			month = 1;
			year++;
		}
	}
    return 0;
}
  1. 时间模拟
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

ll t, time, h, m, s; 

int main(){
	time = 1000*60*60*24; //一天毫秒数 
	cin >> t;
	
	t %= time;//超过一天的部分去除 
	time /= 24;
	
	h = t / time;//整除进制,求当前位 
	t %= time;//求余去掉高位进制 剩下的数 
	time /= 60;//求每一位上的数,除以该进制 
	
	m = t / time;
	t %= time;
	time /= 60;
	
	s = t / time;
	cout << h << " " << m << " " << s << endl;
    return 0;
}

四、背包问题

1、01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

  1. DP定义

    有限状态自动机,有向无环图,有起始节点,有终止节点,每一个节点代表一个状态,任何一个非起始节点,都可以由其他节点转换过来

  2. 特点:

    • 二维图
    • 值、重量
    • 可以求每一个状态,也可以求最后的状态,前面的状态并没有用完所有物品
    • 所选的物品不连续。所以可以用来求最长上升子序列
  3. 模板

#include<iostream>
using namespace std;
const int maxl = 1e3;

int n, bagWeight;
int weight[maxl], value[maxl];
int dp[maxl][maxl];//n个物品,空间大小为bagweight的书包,所装的最大价值
/*
3 4
1 15
3 20
4 30
答案35 
*/
int main(){
	cin >> n >> bagWeight;//物品个数、 背包大小 
	for (int i = 0; i < n; i++)	cin >> weight[i] >> value[i];
	
	for (int i = 0; i < n; i++){
		for (int j = 0;  j <= bagWeight; j++){
			if (j < weight[i])	dp[i + 1][j] = dp[i][j];
			else	dp[i + 1][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
		}
	}
	cout << dp[n][bagWeight] << endl;
	return 0;
}
  1. 变种
  • c\c++ b组 砝码称重

    1. 难点:
      • 背包大小要自己找
      • n个砝码有多少有多少不同的状态
    2. 代码
    #include<iostream>
    #include<cstring>
    using namespace std;
    const int maxl = 107;
    
    int n, w[maxl], m;
    bool dp[maxl][maxl];//用n个砝码,能不能拼出重量为m。
    int ans = 0;//检查最后一行的状态
    
    int main(){
    	memset(dp, 0, sizeof(dp));
    	cin >> n;
    	for (int i = 0; i < n; i++)	{
    		cin >> w[i];
    		m += w[i];
    	}
    	
    	dp[0][0] = 1;
    	for (int i = 0; i < n; i++){
    		for (int j = 0; j <= m; j++){
    			dp[i + 1][j] = dp[i][j] || dp[i][j + w[i]] || dp[i][abs(j - w[i])];
    		}
    	}
    	for (int i = 1; i <= m; i++){
    		if (dp[n][i])	ans++;
    	}
    	cout << ans << endl;
    	return 0;
    }
    

    2、完全背包

    有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品能用无数次,求解将哪些物品装入背包里物品价值总和最大。

  1. 模板
#include<iostream>
#include<cstring>
using namespace std;
const int maxl = 1e3;
/*
输入: 
4 5
1 2
2 4
3 4
4 5
输出;
10 
*/
int n, bagWeight;
int weight[maxl], value[maxl];
int dp[maxl][maxl];//n个物品,空间大小为bagweight的书包,所装的最大价值

int main(){
	memset(dp, 0, sizeof(dp));
	cin >> n >> bagWeight;
	for (int i = 0; i < n; i++)	cin >> weight[i] >> value[i];
	
	for (int i = 0; i < n; i++){
		for (int j = 0; j <= bagWeight; j++){
			if (j < weight[i])	dp[i + 1][j] = dp[i][j];
			else 	dp[i + 1][j] = max(dp[i][j], dp[i + 1][j - weight[i]] + value[i]);
            //唯一与01背包不同的地方,空间足够,第n个物品就一直放
		}
	}
	
	cout << dp[n][bagWeight] << endl;
	return 0;
}

五、排列与组合

排列与组合,基础递归

1、排列

n数个从中抽取k个数排列

1、从初始顺序开始排列

#include<iostream>
using namespace std;
const int maxl = 1e6;

int n, k;//n数个从中抽取k个数排列
int a[maxl], ans[maxl];
bool flag[maxl];
int cnt = 0;//组合数 

void f(int d, bool flag[maxl], int ans[maxl]){// 已经几个数 
	if (d == k){
		cnt++;
		for (int i = 0; i < k; i++)	cout << ans[i] << " ";
		cout << endl;
		return;
	}
	for (int i = 0; i < n; i++){
		if (!flag[i]){
			ans[d] = a[i];
			flag[i] = !flag[i];
			f(d + 1, flag, ans);
			flag[i] = !flag[i];
		}
	}
}

int main(){
	cin >> n >> k;
	for (int i = 0; i < n; i++)	cin >> a[i];
	f(0, flag, ans);
	cout << cnt << endl;//输出排列数 
	return 0;
}

2、从给定的顺序排列

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxl = 1e4 + 7;

int n, m, a[maxl], d[maxl], cnt = 0;
bool flag[maxl];

void f(int step){
	if(cnt > m)	return; 
	if (step == n){
		cnt++;
		if(cnt > m){
			for (int i = 0; i < n; i++)	cout << a[i] << " ";
			return;
		}
	}
	for (int i = 1; i <= n; i++){
		if(!cnt){//第一次排序 
			i = d[step];//强行回到数组d的位置 
		}
		if (!flag[i]){
			flag[i] = !flag[i];
			a[step] = i;
			f(step + 1);
			flag[i] = !flag[i];		
		}
	}
}
int main(){
	memset(flag, 0, sizeof(flag));
	cin >> n >> m;
	for (int i = 0; i < n; i++)	cin >> d[i];
	f(0);
    return 0;
}

2、组合

n数个从中抽取k个数排列

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

int n, m;
int ans[maxl];
bool flag[maxl];

void dfs(int step, int now){
	if (step == m){
		for (int i = 0; i < m; i++)	cout << ans[i] << " ";
		cout << endl;
		return ;
	}
	for (int i = now; i <= n; i++){
		if (!flag[i]){
			flag[i] = 1;//标记已走 
			ans[step] = i;
			dfs(step + 1, i);//ans有值的下标位step, 传dfs的值是step + 1 反正笔者就当成a[step++] = i 所以0 ~ m- - 1
			flag[i] = 0;//回溯 
		}
	}
}
int main(){
	fill(ans, ans + maxl, 0);
	fill(flag, flag + maxl, 0);
	cin >> n >> m;
	dfs(0, 1);
    return 0;
}

六、素数判定

素数:也叫质数,指大于1的自然数中,除了1和该数自身外,无法被其他自然数整除的数

合数:不是素数的自然数

1、试除法(判定单个素数)

#include<iostream>
using namespace std;

int n;
bool flag = 1;

int main(){
	cin >> n; 
	for (int i = 2; i < n/i; i++){
		if (n % i == 0){
			flag = 0;
			break;
		}
	}
	cout << n <<  (flag ? "是素数" : "不是素数") << endl;
	return 0; 
}

2、埃式筛法(2~n的素数)

#include<iostream>
#include<cstring>
using namespace std;
const int maxl = 1e6 + 7;

int n;
bool flag[maxl];

int main(){
	memset(flag, 1, sizeof(flag));
	cin >> n;
	for (int i = 2; i <= n/i; i++){//一样的走一半即可
		if (flag[i]){
			for (int j = i*i; j <= n; j += i){//筛i的倍数
				flag[j] = 0;
			}
		}
	}
	for (int i = 2; i <= n; i++){
		if (flag[i])	cout << i << endl;
	}
	return 0;
}

3、欧拉筛法(2~n)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e5 + 7;

int n, prime[maxl], cnt = 0;//增加的点:存素数的数组、下标cnt 
bool flag[maxl];//埃式筛法优化版本 
 

int main(){
	fill(flag, flag + maxl, 0);
	fill(prime, prime + maxl, 0);
	cin >> n;
	for (int i = 2; i <= n; i++){
		if (!flag[i])	prime[cnt++] = i; //如果是素数,就加入 
		for (int j = 0; j < cnt && i * prime[j] <= n; j++){//这个i充当倍数的角色、所以每次都要进行帅选 
			flag[i * prime[j]] = 1;//筛除每个质数相乘得到的合数 
			if (i % prime[j] == 0)	break;//每个合数只被它的最小质因子筛选一次,以达到不重复的目的 
		}

	}
	for (int i = 0; i < cnt; i++)	cout << prime[i] << " ";//输出所有的素数 
    return 0;
}

七、二分查找

1、用法条件

  1. 整数二分

    • 找上界还是找下界?

      • 上界 =
      • 下届 没有=
    • l的单调性与目标“数组是否相同?

      • 相同 <
      • 不相同 >

2、模板

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull	unsigned long long
#define P pair<int, int>
#define endl '\n'
#define MaxN 0x3f3f3f3f
#define MinN -MaxN
#define llMaxN 2e18
#define llMinN -llMaxN
//#define int ll
const int mod = 1e9 + 7;
const int maxl = 1e6 + 7;

vector<int> a = {0, 1, 2, 3, 4, 5, 5, 5, 8, 9};
vector<int> b = {0, 9, 8, 7, 6, 5, 5, 5, 2, 1}; 
/*
	1. 找上界还是找下届
	2. 单调性与l是否相同 
*/
int upper_z() {
	int l = 0, r = a.size();
	while (l + 1 != r) {
		int mid = l + ((r - l) >> 1);
		if (a[mid] <= 5) l = mid;
		else r = mid;
	}
	return l;
}

int lower_z() {
	int l = 0, r = a.size();
	while (l + 1 != r) {
		int mid = l + ((r - l) >> 1);
		if (a[mid] < 5) l = mid;
		else r = mid;
	}
	return r;
}

int upper_n() {
	int l = 0, r = a.size();
	while (l + 1 != r) {
		int mid = l + ((r - l) >> 1);
		if (b[mid] >= 5) l = mid;
		else r = mid;
	}
	return l;
}

int lower_n() {
	int l = 0, r = a.size();
	while (l + 1 != r) {
		int mid = l + ((r - l) >> 1);
		if (b[mid] > 5) l = mid;
		else r = mid;
	}
	return r;
}

void slove(){
	cout << upper_z() << endl;
	cout << lower_z() << endl;
	cout << upper_n() << endl;
	cout << lower_n() << endl;	
}

signed main(){
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
//	cin >> t;
	while (t--){
		slove();
	}
    return 0;
}

3、STL库函数

  1. binary_search:查找某个元素是否出现。

    • 函数模板:binary_search(arr[],arr[]+size , indx)

    • 参数说明:

      • arr[]: 数组首地址
      • size:数组元素个数
      • indx:需要查找的值
  2. lower_bound:查找第一个大于或等于某个元素的位置。

    • 函数模板:lower_bound(arr[],arr[]+size , indx):
    • 参数说明:
      • arr[]: 数组首地址
      • size:数组元素个数
      • indx:需要查找的值
  3. upper_bound:查找第一个大于某个元素的位置。

    • 函数模板:upper_bound(arr[],arr[]+size , indx):
    • 参数说明:
      • arr[]: 数组首地址
      • size:数组元素个数
      • indx:需要查找的值

八、区间二分

1、流程

  1. 求出每个区间大小
  2. 找出最大的区间长度作为R
  3. 找出mid上升的区间
  4. 输出l

2、模板

#include<iostream>
using namespace std;
const int maxl = 1e7 + 7;

int L, n, k;//公路的长度,原有路标的数量,以及最多可增设的路标数量。
int a[maxl];//两个路标得间距,也即区间间距
int l = 1, r = 0, mid;//l最小为1,因为两个路标之间不可能重合
int before = 0, now;//用来找区间长度

int main(){
    cin >> L >> n >> k;
    for (int i = 0; i < n; i++){
        cin >> now;
        a[i] = now - before;
        before = now;//把 用的过的now设为before 
        r = max(r, a[i]);//寻找最长得区间
    }
    
    a[n] = L - before;//最后一个区间路得末尾到最后一个路障得距离
    r = max(r, a[n]);

    if (k == 0){
		cout << r << endl;
		return 0;
	}
    while (l <= r){//“两闭加等于”看不懂得去翻我的主页二分讲解
        mid = l + ((r - l) >> 1);
		int count = 0;//路障个数
        for (int i = 0; i <= n; i++){//清算以mid(空旷指数)这一段路中的路障个数
            int temp = a[i];
            while (temp > mid){
                count++;
                temp -= mid;
            }
        }
        if (count <= k)	r = mid - 1;//“是闭就添1”
        else	l = mid + 1;
    }
    cout << l << endl;
    return 0;
}

九、DFS与BFS

1、共性

  • 都要开辟一个二维bool类型数组,表示走过。并且每访问一个位置都要标记
  • DFS变量为深度,再遍历方向

2、模板

1.DFS(深度优先遍历)

题目:Lake Counting (POJ No.2386)

  • ​ 把一种方案走到底,以一个为起点,走所有符合方案的位置
#include<iostream>
#include<cstring>
using namespace std;
const int maxl = 107;

int n, m;
char map[maxl][maxl];
bool flag[maxl][maxl];
int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dy[8] = {1, -1, 0, 0, 1, -1, 1, -1};
int ans = 0;

void bfs(int x1, int y1){
	flag[x1][y1] = 1;
	for (int i = 0; i < 8; i++){
		int x = x1 + dx[i];
		int y = y1 + dy[i];
		if (x < 0 || y < 0 || x >= n || y >= m || map[x][y] == '.' || flag[x][y])	continue;
		bfs(x, y);
	}
}
int main(){
	memset(flag, 0, sizeof(flag));
	cin >> n >> m;
	for (int i = 0; i < n; i++){
		for (int j = 0; j < m; j++){
			cin >> map[i][j];
		}
	}
	
	for (int i = 0; i < n; i++){
		for (int j = 0; j < m; j++){
			if (map[i][j] == 'W' && !flag[i][j]){
				bfs(i, j);
				ans++;
			}
		}
	}
	cout << ans << endl;
	return 0;
}

2、BFS(宽度优先遍历)

题目:迷宫的最短路径

  • 样例

    输入:
    10 10
    #S######.#
    ......#..#
    .#.##.##.#
    .#........
    ##.##.####
    ....#....#
    .#######.#
    ....#.....
    .####.###.
    ....#...G#
    输出:
    22
    
  • 作用:求最短的路径

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e2 + 7;
struct point{
	int x, y;
	int step;
};
/*
10 10
#S######.#
......#..#
.#.##.##.#
.#........
##.##.####
....#....#
.#######.#
....#.....
.####.###.
....#...G#
*/
int n, m, gx, gy, sx, sy;
char map[maxl][maxl];
queue<point> q;
point tp;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
bool flag[maxl][maxl];

int bfs(){
	while (!q.empty()){
		tp = q.front();
		q.pop();
		
		for (int i = 0; i < 4; i++){
			int x = tp.x + dx[i];
			int y = tp.y + dy[i];
			if (x < 0 || y < 0 || x >= n || y >= m || map[x][y] == '#' || flag[x][y])	continue;
			flag[x][y] = 1;
			q.push({x, y, tp.step + 1});
			if (map[x][y] == 'G')	return tp.step + 1;
		}
	}
}
int main(){
	memset(flag, 0, sizeof(flag));
	cin >> n >> m;
	for (int i = 0; i < n; i++){
		for (int j = 0; j < m; j++){
			cin >> map[i][j];
			if (map[i][j] == 'S'){
				sx = i;
				sy = j;
				q.push({i, j, 0});
			}
			if (map[i][j] == 'G'){
				gx = i;
				gy = j;
			}
		}
	}
	cout << bfs();
    return 0;
}

十、高精度算法

1、模板

#include<iostream>
#include<cstring>
using namespace std;
const int maxl = 1e5;

int n = 5;
string ans;
int a1[maxl], b1[maxl], c1[maxl];

string add(string s1, string s2){//加法
	string s = ""; 
		memset(a1, 0, sizeof(a1));
		memset(b1, 0, sizeof(b1));
		memset(c1, 0, sizeof(c1));
		int lena = s1.size(), lenb = s2.size(), lenc = max(lena, lenb) + 1;
		for (int i = 0; i < lena; i++)	a1[lena - i] = s1[i] - '0';
		for (int i = 0; i < lenb; i++)	b1[lenb - i] = s2[i] - '0';
		
		for (int i = 1; i <= lenc; i++){
			c1[i] += (a1[i] + b1[i]);
			c1[i + 1] += c1[i] / 10;
			c1[i] %= 10;
		} 
		while (lenc > 0 && c1[lenc] == 0)	lenc--;
		for (int i = lenc; i > 0; i--)	s += (c1[i] + '0');
		return s;
}	

string sub(string s1, string s2){//减法
	string s = ""; 
	memset(a1, 0, sizeof(a1));
	memset(b1, 0, sizeof(b1));
	memset(c1, 0, sizeof(c1));
	int lena = s1.size(), lenb = s2.size(), lenc = max(lena, lenb) + 1;
	for (int i = 0; i < lena; i++)	a1[lena - i] = s1[i] - '0';
	for (int i = 0; i < lenb; i++)	b1[lenb - i] = s2[i] - '0';
	
	for (int i = 1; i <= lenc; i++){
		if (a1[i] < b1[i]){
			a1[i] += 10;
			a1[i + 1]--;
		}
		c1[i] += (a1[i] - b1[i]);
	} 
	while (lenc > 0 && c1[lenc] == 0)	lenc--;
	for (int i = lenc; i > 0; i--)	s += (c1[i] + '0');
	return s;
} 

string mutip(string s1, string s2){//乘法
	string s = ""; 
	memset(a1, 0, sizeof(a1));
	memset(b1, 0, sizeof(b1));
	memset(c1, 0, sizeof(c1));
	int lena = s1.size(), lenb = s2.size(), lenc = lena + lenb;
	for (int i = 0; i < lena; i++)	a1[lena - i] = s1[i] - '0';
	for (int i = 0; i < lenb; i++)	b1[lenb - i] = s2[i] - '0';
	
	for (int i = 1; i <= lena; i++){
		for (int j = 1; j <= lenb; j++){
			c1[i + j - 1] += (a1[i] * b1[j]);
			c1[i + j] += c1[i + j - 1] / 10;
			c1[i + j - 1] %= 10;
		}
	}
	while (lenc > 0 && c1[lenc] == 0)	lenc--;
	for (int i = lenc; i > 0; i--)	s += (c1[i] + '0');
	return s;
} 

int main(){
	ans = "0";
	for (int i = 1; i <= n; i++)	ans = add(ans, to_string(i));//加法 
	cout << ans << endl;
	
	for (int i = 5; i > 3; i--)	ans = sub(ans, to_string(i));//减法
	cout << ans << endl; 
	
	ans = "1";
	for (int i = 1; i <= n; i++)	ans = mutip(ans, to_string(i));//乘法 
	cout << ans << endl;
	return 0;
} 

十一、并查集

1、算法执行过程

  1. 初始化:把每个节点的父(也即f[ ]数组)初始化为自己的序号
  2. 并:把要合并的两个序号的父节点进行合并
  3. 查:查两个节点的父节点是不是同一个节点

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxl = 1e5 + 7;

int n, m, p;
int f[maxl], a, b;

int find(int x){
	if (f[x] == x)	return x;//只有f数组存的整形是自己的下标才是父节点 
	else	return f[x] = find(f[x]);//查找而的时候把他与父节点之间相连 
}
void combine(int x, int y){
	f[find(y)] = find(x);//查找而的时候把父节点之间相连 
}
int main(){
	cin >> n >> m >> p;
	for (int i = 1; i <= n; i++)	f[i] = i;
	for (int i = 1; i <= m; i++){
		cin >> a >> b;
		combine(a, b);
	}
	for (int i = 1; i <= p; i++){
		cin >> a >> b;
		if (find(a) == find(b))	cout << "Yes" << endl;
		else	cout << "No" << endl;
	} 
    return 0;
}

十二、字符串哈希

1、写过程中要注意的地方

  1. 存放哈希值,要开unsigned long long
  2. 进制为13331

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef unsigned long long ull;
const int maxl = 1e5 + 7;
const ull base = 13331;

set<ull> a;
string s;
int n, ans = 1;

int hashe(string s){
	ull ans = 0;
	for (int i = 0; i < int(s.size()); i++)	ans = ans * base + s[i];
	return ans;
}
int main(){
	cin >> n;
	for (int i = 0; i < n; i++){
		cin >> s;
		a.insert(hashe(s));
	}
	cout << a.size() << endl;
    return 0;

十三、图的遍历

1、图的DFS与BFS与普通的区别

  • 普通的会回溯,但是图的就不需要,只要把所有的节点遍历过就行

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int maxl = 1e5 + 7;
struct edge{//边
	int u, v;
};

int n, m, ans[maxl], step = 1;
vector<int> e[maxl];//存节点顺序
vector<edge> s;//存边
bool flag1[maxl], flag2[maxl];
int p, tp;

void dfs( int x){//不回溯。所以只有一个结果 
	flag1[x] = 1;
	cout << x << " ";
	for (int i = 0; i < int(e[x].size()); i++){
		int nextPoint = s[e[x][i]].v;
		if (!flag1[nextPoint])	dfs(nextPoint);
	}
}

void bfs(int x){
	queue<int> q;
	q.push(x);
	cout << x << " ";
	flag2[x] = 1;
	while (!q.empty()){
		tp = q.front();
		q.pop();
		for (int i = 0; i < int(e[tp].size()); i++){
			int nextPoint = s[e[tp][i]].v;
			if (!flag2[nextPoint]){
				q.push(nextPoint);
				cout << nextPoint << " ";
				flag2[nextPoint] = 1;
			}
		}
	}
}

bool cmp(edge a, edge b){//让节点排好序,给节点赋编号
	if (a.u != b.u)	return a.u < b.u;
	else	return a.v < b.v;
}
int main(){
	memset(flag1, 0, sizeof(flag1));
	memset(flag2, 0, sizeof(flag2));
	cin >> n >> m;
	for (int i = 0; i < m; i++){
		int uu, vv;
		cin >> uu >> vv;
		s.push_back((edge){uu, vv});
	}
	
	sort(s.begin(), s.end(), cmp);
	for (int i = 0; i < m; i++){
		e[s[i].u].push_back(i);
	}
	dfs(1);
	cout << endl;
	bfs(1);
    return 0;
}

十四、拓扑排序(有向图)

1、两种实现方法DFS和BFS

  • DFS:
    • 创建一个数组表示节点状态,-1代表来过了,0代表还没有来过、1代表成功加入结果数组vector
      • 首先对于每一个节点,第一次遍历都是0(初始化为零)
      • 第二次遍历到该节点是-1,说明来过该节点,且他没有加入结果数组vector,说明成环。此图不能拓扑排序,直接返回零
      • 第二次遍历到该节点是1,说明该节点,已经成功加入到vector数组,所以不用遍历其子节点
  • BFS:找一个数组记录入度数、从入度为零的节点作为起始节点、遍历后续节点、如果遇到节点的入度为零就加入队列重复

2、样例

输入:
8 8
1 3
3 5
2 4
4 6
4 5
5 7
7 8
6 8
输出:(只写了DFS和BFS结果的两种情况)
2 4 6 1 3 5 7 8 or 1 2 3 4 6 5 7 8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fzI9kZXG-1680849239370)(D:\桌面\模板图片\十四、拓扑排序.png)]

样例

3、DFS

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> p;
const int maxl = 1e5 + 7;
/*
输入:
8 8
1 3
3 5
2 4
4 6
4 5
5 7
7 8
6 8
输出:
2 4 6 1 3 5 7 8 or 1 2 3 4 6 5 7 8
*/
int n, m, u, v;
vector<int> G[maxl], ans;
int flag[maxl]; 

bool dfs(int s){
	flag[s] = -1;//标记当前节点状态,表示来过
	for (int nextP : G[s]){//遍历子节点 
		if (flag[nextP] == -1)	return 0;//与子节点成环,此图拓扑不成功,直接返回零
		if (!flag[nextP]) {//如果当前节点没有来过 
			if (!dfs(nextP)) return 0;//子节点的子节点也没有成环的 
		}
	} 
	
	flag[s] = 1;
	ans.push_back(s);
	return 1; 
} 
bool toupu(){
	fill(flag, flag + maxl, 0);
	for (int i = 1; i <= n; i++){
		if (!flag[i]){//没访问过的节点加入 
			if (!dfs(i))	return 0;//如果子节点成环,就不能成拓扑排序,直接返回零 
		}
	}
	reverse(ans.begin(), ans.end()); 
	return 1;
}
int main(){
	cin >> n >> m;
	for (int i = 0; i < m; i++){
		cin >> u >> v;
		G[u].push_back(v);
	}
	if (toupu())	for (int value : ans)	cout << value << " ";
	else	cout << "orz";//不能拓扑排序 
    return 0;
}

4、BFS

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> p;
const int maxl = 1e5 + 7;
/*
输入:
8 8
1 3
3 5
2 4
4 6
4 5
5 7
7 8
6 8
输出:
2 4 6 1 3 5 7 8 or 1 2 3 4 6 5 7 8
*/
int n, m, u, v;
vector<int> G[maxl], ans;
queue<int> q;
int preCnt[maxl], tp;

bool tuopu(){
	for (int i = 1; i <= n; i++){//遍历每个节点
		if (preCnt[i] == 0){//让入度为零的节点,加入队列 
			q.push(i);
		}
	}
	while (!q.empty()){
		tp = q.front();
		q.pop();
		
		ans.push_back(tp);//把当前节点加入数组保存起来 
		
		for (int nextP : G[tp]){
			preCnt[nextP]--;//当前节点入度数-- 
			if (!preCnt[nextP])	q.push(nextP);//入度数为零就加入队列 
		}
	}
	return int(ans.size()) == n;
}
int main(){
	fill(preCnt, preCnt + maxl, 0);//每个入度节点数初始化为零 
	cin >> n >> m;
	for (int i = 0; i < m; i++){
		cin >> u >> v;
		G[u].push_back(v);
		preCnt[v]++;//子节点的入度节点数+1 
	}
	if (tuopu())	for (int value : ans)	cout << value << " ";
	else	cout << "orz";//成环、不能拓扑排序 
    return 0;
}

十五、最小生成树(prim堆优化算法)

1、定义:一副连通加权无向图中一棵权值最小的生成树

  • 简而言之:相连的两个节点之间的权值最小。

2、堆优化策略

  • 把边放进优先队列、按升序排列,每次队首元素(权重最小)——实现了最小生成树
  • 选过节点后,就用flag数组标记,避免重复计算权值

3、样例

输入:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出:(最小生成树的权值和)
7

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHGi0ngH-1680849239371)(D:\桌面\模板图片\十五、最小生成树png.png)]

样例图

4、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;//first ——当前节点与父节点的边的权重 、second —— 当前节点 
const int maxl = 1e5 + 7;
struct edge{
	int point;//当前点 
	int weight;	//当前点与子节点相连的边的权重 
};

int n, m, u, v, w;
vector<edge> G[maxl];
priority_queue<P, vector<P>, greater<P>> q;
P tp;
int ans = 0, cnt = 1;//ans ——权值和、cnt ——已经遍历过的节点数 
bool flag[maxl];//标记数组 

void prim(int s){
	flag[s] = 1;//标记该节点 
	for (edge nextP : G[s]){
		q.push({nextP.weight, nextP.point});//first ——当前节点与父节点的边的权重 、second —— 当前节点 
	}
	while (!q.empty()){
		tp = q.top();
		q.pop();
		int nowP = tp.second;//把当前节点,单独写出来,减轻逻辑负担 
		int nowWeight = tp.first;
		
		if (flag[nowP])	continue;//来过了,就没必要再遍历当前节点的子节点了 
        
         flag[nowP] = 1;//标记 
         ans += nowWeight;//加上当前节点权值 
         cnt++;//满足要求的节点数加一 
		for (edge nextP : G[nowP]){//遍历子节点 
			if (!flag[nextP.point]){//把没来过的子节点加入队列 
				q.push({nextP.weight, nextP.point});
			}
		}
	}
}
int main(){
	fill(flag, flag + maxl, 0);
	cin >> n >> m;
	for (int i = 0; i < m; i++){
		cin >> u >> v >> w;
		G[u].push_back({v, w});//无向边、所以要加u->v 和 v->u 
		G[v].push_back({u, w});
	}
	prim(1);//笔者是从1节点开始, 从其他节点开始也行 
	if (cnt == n)	cout << ans << endl;
	else	cout << "orz" << endl;
    return 0;
}

5、prim堆优化算法评价

直接把子节点和其权重加入优先队列标记

简而言之:直接加点、权,并标记。遍历即可

十五 、dijkstra堆优化算法(单源最短路)

1、实现最关键的地方

  • 和prim算法一样,唯一的区别,就是加上了一个数组d,表示到唯一单源的最短路径

  • 判断该节点到底来过没有的写法

    • if (d[nowP] < nowWeight)	continue;//d[nowP]初始值是 2147483647,现在如果小于 nowWeight说明这节点遍历过了,就不用遍历了 
      

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e3 + 7;
struct edge{
	int point;
	int weight;
} ;

int n, m, s, u, v, w;
vector<edge> G[maxl];
priority_queue<P, vector<P>, greater<P> > q;//first ——当前节点与父节点的边的权重 、second —— 当前节点 
int d[maxl];//单元最短路径,从某一个节点到其他节点得最小距离 
P tp;

void djs(int s){
	d[s] = 0;
	q.push({0, s});//这里像prim算法把子节点放入也行,但是不放一样得 
	
	while (!q.empty()){
		tp = q.top();
		q.pop();
		int nowP = tp.second;
		int nowWeight = tp.first;
		
		if (d[nowP] < nowWeight)	continue;//d[nowP]初始值是 2147483647,现在如果小于 nowWeight说明这节点遍历过了,就不用遍历了 
		for (edge nextP : G[nowP]){
			if (nextP.weight + d[nowP] < d[nextP.point]){//如果加上通向子节点得这条前向权,比原来得距离更小。那么就更新d 
				d[nextP.point] = nextP.weight + d[nowP];
				q.push({d[nextP.point], nextP.point});//并且把子节点放入队列 
			}
		}
	}
}

int main(){
	fill(d, d + maxl, 2147483647);//2^31 - 1不是特殊值,是题目上得 
	cin >> n >> m >> s;
	for (int i = 0; i < m; i++){
		cin >> u >> v >> w;
		G[u].push_back({v, w});//有向边的带非负权图
	}
	djs(s);
	for (int i = 1; i <= n; i++)	cout << d[i] << " ";//这是其余节点到目标节点得距离 
    return 0;
}

c
if (d[nowP] < nowWeight) continue;//d[nowP]初始值是 2147483647,现在如果小于 nowWeight说明这节点遍历过了,就不用遍历了
~~~

2、模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<list>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int maxl = 1e3 + 7;
struct edge{
	int point;
	int weight;
} ;

int n, m, s, u, v, w;
vector<edge> G[maxl];
priority_queue<P, vector<P>, greater<P> > q;//first ——当前节点与父节点的边的权重 、second —— 当前节点 
int d[maxl];//单元最短路径,从某一个节点到其他节点得最小距离 
P tp;

void djs(int s){
	d[s] = 0;
	q.push({0, s});//这里像prim算法把子节点放入也行,但是不放一样得 
	
	while (!q.empty()){
		tp = q.top();
		q.pop();
		int nowP = tp.second;
		int nowWeight = tp.first;
		
		if (d[nowP] < nowWeight)	continue;//d[nowP]初始值是 2147483647,现在如果小于 nowWeight说明这节点遍历过了,就不用遍历了 
		for (edge nextP : G[nowP]){
			if (nextP.weight + d[nowP] < d[nextP.point]){//如果加上通向子节点得这条前向权,比原来得距离更小。那么就更新d 
				d[nextP.point] = nextP.weight + d[nowP];
				q.push({d[nextP.point], nextP.point});//并且把子节点放入队列 
			}
		}
	}
}

int main(){
	fill(d, d + maxl, 2147483647);//2^31 - 1不是特殊值,是题目上得 
	cin >> n >> m >> s;
	for (int i = 0; i < m; i++){
		cin >> u >> v >> w;
		G[u].push_back({v, w});//有向边的带非负权图
	}
	djs(s);
	for (int i = 1; i <= n; i++)	cout << d[i] << " ";//这是其余节点到目标节点得距离 
    return 0;
}
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值