一、约数
求一个数的所有约数
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、模板
- 天数模拟
#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;
}
- 时间模拟
#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] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
-
DP定义
有限状态自动机,有向无环图,有起始节点,有终止节点,每一个节点代表一个状态,任何一个非起始节点,都可以由其他节点转换过来
-
特点:
- 二维图
- 值、重量
- 可以求每一个状态,也可以求最后的状态,前面的状态并没有用完所有物品
- 所选的物品不连续。所以可以用来求最长上升子序列
-
模板
#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;
}
- 变种
-
c\c++ b组 砝码称重
- 难点:
- 背包大小要自己找
- n个砝码有多少有多少不同的状态
- 代码
#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] 。每件物品能用无数次,求解将哪些物品装入背包里物品价值总和最大。
- 难点:
- 模板
#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、用法条件
-
整数二分
-
找上界还是找下界?
- 上界 =
- 下届 没有=
-
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库函数
-
binary_search:查找某个元素是否出现。
-
函数模板:binary_search(arr[],arr[]+size , indx)
-
参数说明:
- arr[]: 数组首地址
- size:数组元素个数
- indx:需要查找的值
-
-
lower_bound:查找第一个大于或等于某个元素的位置。
- 函数模板:lower_bound(arr[],arr[]+size , indx):
- 参数说明:
- arr[]: 数组首地址
- size:数组元素个数
- indx:需要查找的值
-
upper_bound:查找第一个大于某个元素的位置。
- 函数模板:upper_bound(arr[],arr[]+size , indx):
- 参数说明:
- arr[]: 数组首地址
- size:数组元素个数
- indx:需要查找的值
八、区间二分
1、流程
- 求出每个区间大小
- 找出最大的区间长度作为R
- 找出mid上升的区间
- 输出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、算法执行过程
- 初始化:把每个节点的父(也即f[ ]数组)初始化为自己的序号
- 并:把要合并的两个序号的父节点进行合并
- 查:查两个节点的父节点是不是同一个节点
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、写过程中要注意的地方
- 存放哈希值,要开unsigned long long
- 进制为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数组,所以不用遍历其子节点
- 创建一个数组表示节点状态,-1代表来过了,0代表还没有来过、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;
}