每日一练2023.4.17-2023.4.18

数轴上的bfs

农夫约翰被通知,他的一只奶牛逃逸了!所以他决定,马上出发,尽快把那只奶牛抓回来.

    他们都站在数轴上.约翰在N(0≤N≤100000)处,奶牛在K(0≤K≤100000)处.约翰有

两种办法移动,步行和瞬移:步行每秒种可以让约翰从x处走到x+1或x-1处;而瞬移则可让他在1秒内从x处消失,在2x处出现.然而那只逃逸的奶牛,悲剧地没有发现自己的处境多么糟糕,正站在那儿一动不动.

    那么,约翰需要多少时间抓住那只牛呢?

输入格式

* Line 1: Two space-separated integers: N and K

    仅有两个整数N和K.

输出格式

* Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.

    最短的时间.

样例

输入样例:

5 17
Farmer John starts at point 5 and the fugitive cow is at point 17.

输出样例:

4
OUTPUT DETAILS:

The fastest way for Farmer John to reach the fugitive cow is to
move along the following path: 5-10-9-18-17, which takes 4 minutes.

 广搜 - -队列

每次取队头元素出队列,对于该队头节点,可能就是要求的节点,成功,退出;也可能不是,不是的话,利用该队头元素扩展与该节点有边相连且没有被访问的节点,将新的节点入队 ,直到队为空,所有图中相连的节点都被访问过。

 使用队列:

手写队列,队列有两个指针-front和rear,对于这两个指针,操作都是+1就是上移,如果需要构造循环队列,则需要进行取余对队列长度queueLength进行操作,队列为空等价于front不等于rear,即队首队尾指针不同,稍微整理代码如下,

///普通队列
    #define MAXLENGTH 100000
    int que[MAXLENGTH + 50];

    int rear = 0;
	int front = 0;

    while (rear != front){

        que[rear] = 入队元素值;
		rear++;
        //出队
        front = front + 1;
}

进行循环队列
	#define MAXLENGTH 100000
    int que[MAXLENGTH];

    int rear = 0;
	int front = 0;

    while (rear != front){

        que[rear] = 入队元素值;
		rear=(rear+1)%MAXLENGTH;
        //出队
        front = (front + 1)%MAXLENGTH;
}

参考代码:

#include <stdio.h>
#define MAXLENGTH 100000
int que[MAXLENGTH + 50];
int flag[MAXLENGTH + 50];
int main() {
	//int dp[MAXLENGTH+50];

	int rear = 0;
	int front = 0;
	
	int s, t;
	scanf("%d %d", &s, &t);
	que[0] = s;
	//front=(front+1)%(MAXLENGTH+50);
	rear = rear + 1;
	flag[s] = 1;
	int i = s;
	int cnt = 0;
	while (rear != front) {
		if (flag[t] == 1) {
			printf("%d\n", cnt);
			return 0;
		}
		cnt++;//步数加一

		int loopf = front;
		int loopr = rear;
		for (int j = loopf; j != loopr; j++)
		{
			int tt = que[j];
			//在范围内且没有走过 
			if (tt * 2 <= MAXLENGTH && flag[tt * 2] == 0) {
				
				que[rear] = tt * 2;
				flag[tt * 2] = 1;rear++;
			}
			if (tt + 1 <= MAXLENGTH && flag[tt + 1] == 0) {
				
				que[rear] = tt + 1;
				flag[tt + 1] = 1;rear++;
			}

			if (tt - 1 >= 0 && flag[tt - 1] == 0) {
				
				que[rear] = tt - 1;
				flag[tt - 1] = 1;rear++;
			}
			front = front + 1;
		}


	}

	return 0;
}

如果不仅要输出所需时间,还需要输出路径,如何编程实现

设置每个点的前驱并进行记录

/*
Time Limit: 1000 ms
Memory Limit: 256 mb
农夫John的奶牛跑路了。将地图视作一条数轴,
John的初始位置在s而奶牛的位置在t(0<=s,t<=100000)。John可以花费一分钟的时间使自己作如下移动:

1 从点x移动到点x+1
2 从点x移动到点x-1
3 从点x移动到点x*2
奶牛的位置一直在点t。现在给定s,t,要求John要追上奶牛最少需要几分钟。
*/

#include <stdio.h>
#define MAXLENGTH 100000//设置最大的数
int que[MAXLENGTH + 50];//记录队列,用于bfs广度优先搜索
int flag[MAXLENGTH + 50];//记录是否走过,1表示走过,0表示未走过
int mapp[MAXLENGTH + 50];//记录前驱
int pathp[MAXLENGTH + 50];//记录路径,用于输出路径
int main() {
	//队尾指针
	int rear = 0;
	//队首指针
	int front = 0;
	
	int s, t;
	scanf("%d %d", &s, &t);
	que[0] = s;//起点入队
	//front=(front+1)%(MAXLENGTH+50);
	rear = rear + 1;//尾指针移动
	flag[s] = 1;//标记,该点已走过
	int cnt = 0;//计数,当前所走时间
	while (rear != front) {
		if (flag[t] == 1) {
			//到达终点,输出时间和路径
			//输出时间
			printf("%d\n", cnt);
			//路径转换--记录是前驱,需要先得到路径再逆序输出
			int tmp;
			tmp = t;
			//printf("%d<-", tmp);
			int itmp = 0;
			pathp[itmp] = tmp;
			itmp++;
			while (1) {
				if (mapp[tmp] == s)
				{
					//printf("%d", mapp[tmp]);
					pathp[itmp] = mapp[tmp];
					//itmp++;
					break;
				}
				else {
					//printf("%d<-", mapp[tmp]);
					pathp[itmp] = mapp[tmp];
					itmp++;
					tmp= mapp[tmp];
				}
			}
			//输出起点
			printf("%d", pathp[itmp]);//print s
			//输出路径,直到终点
			for (int j = itmp-1; j >= 0; j--) {
				printf("->%d", pathp[j]);//print s
			}
			return 0;
		}
		cnt++;//步数加一
		//记录当前队列队首队尾指针
		int loopf = front;
		int loopr = rear;
		//当前队列数据出队并压入新的数据
		for (int j = loopf; j != loopr; j++)
		{
			int tt = que[j];
			//在范围内且没有走过 
			if (tt - 1 >= 0 && flag[tt - 1] == 0) {

				que[rear] = tt - 1;//入队
				flag[tt - 1] = 1;//标记
				mapp[tt - 1] = tt;//前驱
				rear++;//入队
			}
			if (tt * 2 <= MAXLENGTH && flag[tt * 2] == 0) {
				
				que[rear] = tt * 2;//入队
				flag[tt * 2] = 1;//标记
				mapp[tt * 2] = tt;//前驱
				rear++;//入队
			}
			if (tt + 1 <= MAXLENGTH && flag[tt + 1] == 0) {
				
				que[rear] = tt + 1;//入队
				flag[tt + 1] = 1;//标记
				mapp[tt + 1] = tt;//前驱
				rear++;//入队
			}
			front = front + 1;//出队
		}
	}
	return 0;
}

二维平面上的bfs

定义一个二维数组:

int maze[5][5] = {

0, 1, 0, 0, 0,

0, 1, 0, 1, 0,

0, 0, 0, 0, 0,

0, 1, 1, 1, 0,

0, 0, 0, 1, 0,

};

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

输入:一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
输出:左上角到右下角的最短路径,格式如样例所示。
样例输入:

0, 1, 0, 0, 0,

0, 1, 0, 1, 0,

0, 0, 0, 0, 0,

0, 1, 1, 1, 0,

0, 0, 0, 1, 0,

复制
样例输出:
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

提示:

在一个点访问完后,并且拓展出其周围全部的点后,并不能就丢弃,

而是应该储存起来;
然后通过下标,回溯寻找出路径。

输出路径,可以参考链表或者迪杰斯特拉算法等,对于一个点,设置其前继,即该点前面的点,由此可以得到路径。

#include<stdio.h>
int maze[10][10];
struct point {
	int x;
	int y;
};
struct point que[100];
int flag[10][10];//记录是否走过 
struct point  mapp[10][10];//记录(i,j)的前驱 
struct point path[100];//记录路径用于输出 

int main() {
	for (int i = 0; i < 5; i++)
		for (int j = 0; j < 5; j++) {
			scanf("%d,", &maze[i][j]);
		}
	int front = 0;
	int rear = 0;
	que[0].x = 0;//入队 
	que[0].y = 0;
	rear++;
	flag[0][0] = 1;//左上角点标记 
	int cnt = 0;
	while (1) {
		if (flag[4][4] == 1) {//到达终点 
			printf("路径长度是%d\n", cnt);
			//路径转换并输出 
			int pathcnt = 0;
			//path[0].x = 4;
			//path[0].y = 4;
			//pathcnt++;
			struct point pointtmp;
			pointtmp.x = 4;
			pointtmp.y = 4;

			while (1) {
				int tmppathx = pointtmp.x;
				int tmppathy = pointtmp.y;
				if (tmppathx == 0 && tmppathy == 0) {
					//到达起点 
					path[pathcnt].x = 0;
					path[pathcnt].y = 0;
					break;
				}
				else {//迭代 
					path[pathcnt].x = tmppathx;
					path[pathcnt].y = tmppathy;
					pathcnt++;//下标加一 
					pointtmp.x = mapp[tmppathx][tmppathy].x;
					pointtmp.y = mapp[tmppathx][tmppathy].y;
				}
			}
			//输出起点
			for (int printi = pathcnt; printi >= 0; printi--) {
				printf("(%d,%d)\n", path[printi].x, path[printi].y);
			}
			break;
		}
		cnt++;
		int rear_ = rear;
		int front_ = front;
		for (int i = front_; i != rear_; i++) {
			//寻找可能入队的
			int xtmp = que[i].x;
			int ytmp = que[i].y;
			int next[4][2] = { 1,0,0,1,-1,0,0,-1
			};
			for (int nexti = 0; nexti < 4; nexti++) {
				int nextx = xtmp + next[nexti][0];
				int nexty = ytmp + next[nexti][1];
				if (nextx < 5 && nexty < 5 && nextx >= 0 && nexty >= 0 && maze[nextx][nexty] == 0 && flag[nextx][nexty] == 0) {
					que[rear].x = nextx;//入队 
					que[rear].y = nexty;
					rear++;
					flag[nextx][nexty] = 1;//标记 
					mapp[nextx][nexty].x = xtmp;//记录前驱 
					mapp[nextx][nexty].y = ytmp;
				}
			}
			front++;//出队 
		}
	}
	return 0;
}

bfs

 如图所示: 有9只盘子,排成1个圆圈。其中8只盘子内装着8只蚱蜢,有一个是空盘。


我们把这些蚱蜢顺时针编号为 1~8。每只蚱蜢都可以跳到相邻的空盘中,也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。
请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,并且保持空盘的位置不变(也就是1-8换位,2-7换位,...),至少要经过多少次跳跃? 

思路:多动不如一动,考虑空盘子在动,

盘子的状态使用数字表示,数字有很多,9*8*7*6*5*4*3*2*1种排列情况,但是使用数字直接表示需要更多的数据内存,会报错,使用map完成映射,缩小数组元素个数;其余就是bfs常规部分,代码如下,

#include<stdio.h>
#include <map>
#include <iostream>
#include <algorithm>
#define MAXSIZE 654321

struct point {
	int x[10];
};
#define MAXNUM 9
int que[MAXSIZE];
int flag[MAXSIZE];//记录是否走过  

using namespace std;
map<int,int> mp;

int main() {

	int rear = 0;
	int front = 0;
	struct point init;
	for (int i = 0; i < MAXNUM; i++) {
		init.x[i] = i;
	}
	struct point expo;
	expo.x[MAXNUM-1] = 1;
	for (int i = MAXNUM-2; i >= 0; i--) {
		expo.x[i] = expo.x[i+1] * 10;
	}
	int flagNum = 0;
	for (int i = 0; i < MAXNUM; i++) {
		flagNum += i * expo.x[i];
	}
	int endNum = 0;
	for (int i = 0; i < MAXNUM; i++) {
		endNum += i * expo.x[MAXNUM-i];
	}

	//实现映射
	//87654321->8*7*6*5*4*3*2*1
	int a[MAXNUM];
	for (int i = 0; i < MAXNUM; i++) {
		a[i] = i;
	}
	int mulRes = 1;
	int uppernum = MAXNUM;
	//printf("%d\n", uppernum);
	for (int i = 1; i <= uppernum; i++)
		mulRes = mulRes * i;
	//printf("%d", mulRes);
	for (int i = 0; i < mulRes; i++) {
		int sum = 0;
		for (int j = 0; j < MAXNUM; j++)
			sum += a[j] * expo.x[j];
		mp[sum] = i;
		next_permutation(a, a + MAXNUM);
	}


	//队列操作
	que[0] = flagNum;
	rear++;
	flag[mp[flagNum]] = 1;//标记 
	int cnt = 0;
	while (1) {
		//87654321--9
		if (flag[mp[endNum]] == 1) {
			printf("%d\n", cnt);
			break;
		}
		else {
			cnt++;
			int front_ = front;
			int rear_ = rear;
			for (int i = front_; i != rear_; i++) {
				int tempPointNum = que[i];
				//printf("%d\n", tempPointNum);
				struct point pointNow;
				int pos0;
				//数字转换为数组 
				for (int j = 0; j < MAXNUM; j++) {
					//printf("%d %d\n", j, tempPointNum / expo.x[j]);
					pointNow.x[j] = tempPointNum / expo.x[j];
					if (pointNow.x[j] == 0)pos0 = j;//找到位置 
					tempPointNum= tempPointNum- pointNow.x[j]* expo.x[j];
				}

				//数组变换,得到新的数
				int nextOp[4] = { 1,-1,2,-2 };
				for (int transi = 0; transi < 4; transi++) {
					//struct point pointNow;
					int trans2 = (pos0 + nextOp[transi] + MAXNUM) % MAXNUM;
					int arr2num = 0;
					for (int numi = 0; numi < MAXNUM; numi++) {
						if (numi == trans2) {
							arr2num += pointNow.x[pos0] * expo.x[numi];
							continue;
						}
						if (numi == pos0) {
							arr2num += pointNow.x[trans2] * expo.x[numi];
							continue;
						}
						arr2num += pointNow.x[numi] * expo.x[numi];
					}

					if (flag[mp[arr2num]] == 0) {
						que[rear] = arr2num;
						rear++;//入队 
						flag[mp[arr2num]] = 1;
					}
				}
				front++;//出队 
			}
		}
	}


	return 0;
}

4.23 数颜色

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn = 200050;
struct node {
	int parent;
	int color_index;
	vector<int> children;
};
int n;
//1表示 不是叶子,0表示是叶子节点 
int Leaf[maxn];

struct node tree_nodes[maxn];

void dfs(int root) {
	//遍历root树
	if (Leaf[root] == 0) {
		printf("%d的颜色是%d\n", root, tree_nodes[root].color_index);
		return;
	}
	else {
		int child_size = tree_nodes[root].children.size();
		for (int i = 0; i < child_size; i++) {
			//遍历保证只访问一次 
			printf("tree_nodes[root].children[i] %d\n", tree_nodes[root].children[i]);
			dfs(tree_nodes[root].children[i]);//访问孩子节点i 
		}
		printf("%d的颜色是%d\n", root, tree_nodes[root].color_index);
		return;
	}

}

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);     //加速(很关键!)
	cin>>n;
	int ci, fi;
	for (int i = 1; i <= n; i++) {
		cin >> ci >> fi;
		Leaf[i] = 1;
		tree_nodes[i].parent = fi;
		tree_nodes[i].color_index = ci;
		tree_nodes[fi].children.push_back(i);
	}
	dfs(1);

	return 0;
}

4.24

dijstra O(n*n)

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
//n个点m个边
//a是邻接矩阵
//d存储最短路径 
int n,m,a[205][205],d[205];
//v存储访问与否的标记 
bool v[205];
int main(){
	cin>>n>>m;//输入n和m 
	memset(a,0x3f,sizeof(a));//填充 
	for(int i = 1;i <= m; i++){
		int x,y,z;
		scanf("%d %d %d", &x,&y,&z);
		a[x][y]=z;//邻接矩阵输入 
	}
	for(int i =1;i <n;i++){
		a[i][i]=0;//自己到自己是0 
	}
	memset(d,0x3f,sizeof(d));//初始化最短路径数组 
	d[1]=0;//1到自己距离是0 
	//遍历所有点 
	for(int cnt = 1; cnt <= n;cnt++){
		//找未拓展里面的d最短的 转移至已拓展 
		int min_val=1<<30,x;//初始化 最短距离和相应节点x 
		for(int i = 1;i <= n;i ++){
			if(!v[i] && d[i]<min_val){//未拓展里最小的 
				min_val=d[i],x=i;
			}
		}
		v[x]=true;//将x放入已拓展集合
		//更新所有集合点的最短路径 
		for(int y = 1 ;y <= n;y++){
			d[y]=min(d[y],d[x]+a[x][y]);
		}
	}
	if(d[n]==0x3f3f3f3f)puts("-1");//无穷大没有更新 最短路径 
	else cout<<d[n]<<endl;//输出最短路径 
	return 0; 
}

dijkstra O(mlogn)

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int max_n = 150005,max_m = 150005;
//邻接表
int head[max_n],ver[max_m],edge[max_m],nxt[max_m],tot=0;
void add(int x, int y, int z){
	tot++;
	ver[tot]=y;
	edge[tot]=z;
	nxt[tot]=head[x];
	head[x]=tot;
} 
int n,m,d[max_n];
bool v[max_n];
//pair<-dist[x],x>
priority_queue<pair<int,int> > q;//大根堆 
int main(){
	cin>>n>>m;//输入n和m 
	for(int i = 1;i <= m;i++){
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		add(x,y,z);
	}
	memset(d,0x7f,sizeof(d));
	d[1]=0;
	q.push(make_pair(0,1));
	while(!q.empty()){
		int x=q.top().second;
		q.pop();
		if(v[x])continue;
		v[x]=true;
		for(int i =head[x];i;i=nxt[i]){
			int y=ver[i],z=edge[i];
			if(d[y]>d[x]+z){
				d[y]=d[x]+z;
				q.push(make_pair(-d[y],y));
			}
		}
	}
	if(d[n]==0x7f7f7f7f)puts("-1");
	else cout << d[n]<<endl;
	return 0; 
}

# [USACO06JAN]The Cow Prom S

## 题目描述

有一个 $n$ 个点,$m$ 条边的有向图,请求出这个图点数大于 $1$ 的强联通分量个数。

## 输入格式

第一行为两个整数 $n$ 和 $m$。

第二行至 $m+1$ 行,每一行有两个整数 $a$ 和 $b$,表示有一条从 $a$ 到 $b$ 的有向边。

## 输出格式

仅一行,表示点数大于 $1$ 的强联通分量个数。

## 样例 #1

### 样例输入 #1

```
5 4
2 4
3 5
1 2
4 1
```

### 样例输出 #1

```
1
```

## 提示

#### 数据规模与约定

对于全部的测试点,保证 $2\le n \le 10^4$,$2\le m\le 5\times 10^4$,$1 \leq a, b \leq n$。

#include<bits/stdc++.h>
#include<iostream>
#include<vector>
//##Targan 算法
//##1.时间戳  dfn[x] : 节点x第一次被访问的顺序 
//##2.追溯值 low[x]  :从节点x出发,所能访问的最早时间戳

//#1.入 x时,盖戳,入栈
//#2。枚举x的邻点,分为三种情况
//###2.1若y尚未访问,对y深搜,用low[y]更新low[x],
//#####x是y的父节点,y能访问的点,父节点x一定也能访问到
//###2.2 若y已经访问并且在栈中,
//######说明y是祖先节点,或者是左子树节点,用dfn[y]更新Low[x]
//###2.3 若y已经访问并且不在栈中,说明y已经搜索完毕
//######其所在连通分量已经被处理,不需要对y进行操作
//#3.离x时,记录SCC,只有遍历了一个SCC,才可以对其出栈
//#####更新low值的意义,避免SCC的节点提前出栈

#include<vector> 
using namespace std;

const int N = 10050;
vector<vector<int> > e;

int dfn[N], low[N], tot;//时间戳,追溯值,时间戳计数器 
int stk[N], instk[N], top;//栈 , 
int scc[N], siz[N], cnt;//强连通分量,编号 

void tarjan(int x) {
	//进入一个点,操作如下 
	//#1.入 x时,盖戳,入栈
	dfn[x] = low[x] = ++tot;
	stk[++top] = x, instk[x] = 1;
	//#2。枚举x的邻点,分为三种情况
	//###2.1若y尚未访问,对y深搜,用low[y]更新low[x],
	//#####x是y的父节点,y能访问的点,父节点x一定也能访问到
	//###2.2 若y已经访问并且在栈中,
	//######说明y是祖先节点,或者是左子树节点,用dfn[y]更新Low[x]
	//###2.3 若y已经访问并且不在栈中,说明y已经搜索完毕
	//######其所在连通分量已经被处理,不需要对y进行操作
	int ex_size = e[x].size();
	vector<int> ex = e[x];
	for (int j = 0; j < ex_size; j++) {
		int y = ex[j];
		if (!dfn[y]) {
			//case y尚未访问
			tarjan(y);
			//返回x调用函数,子节点y的low值更新父节点x的low值 
			low[x] = min(low[x], low[y]);
		}
		else if (instk[y])//y访问且在栈中 
			low[x] = min(low[x], dfn[y]);
	}
	//#3.离x时,记录SCC,只有遍历了一个SCC,才可以对其出栈
	//#####更新low值的意义,避免SCC的节点提前出栈
	if (dfn[x] == low[x]) {
		//x是SCC的根
		int y; ++cnt;
		do {
			y = stk[top--];
			instk[y] = 0;
			scc[y] = cnt;//SCC编号 
			++siz[cnt];//SCC大小 
		} while (y != x);
	}

}
int main() {
	int n, m;
	scanf("%d %d", &n, &m);
	int u, v;
	for (int i = 0; i < n + 10; i++) {
		vector<int> a;
		e.push_back(a);
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &u, &v);
		e[u].push_back(v);
	}
	for (int start = 1; start <= n; start++) {
		if(!dfn[start])tarjan(start);
	}
	
	//tarjan(1);
	int ans = 0;
	for (int i = 1; i <= cnt; i++) {
		if (siz[i] > 1)
			ans++;
	}
	printf("%d\n", ans);
	return 0;
}

另一种vector写法如下

#include<bits/stdc++.h>
#include<iostream>
#include<vector>
//##Targan 算法
//##1.时间戳  dfn[x] : 节点x第一次被访问的顺序 
//##2.追溯值 low[x]  :从节点x出发,所能访问的最早时间戳

//#1.入 x时,盖戳,入栈
//#2。枚举x的邻点,分为三种情况
//###2.1若y尚未访问,对y深搜,用low[y]更新low[x],
//#####x是y的父节点,y能访问的点,父节点x一定也能访问到
//###2.2 若y已经访问并且在栈中,
//######说明y是祖先节点,或者是左子树节点,用dfn[y]更新Low[x]
//###2.3 若y已经访问并且不在栈中,说明y已经搜索完毕
//######其所在连通分量已经被处理,不需要对y进行操作
//#3.离x时,记录SCC,只有遍历了一个SCC,才可以对其出栈
//#####更新low值的意义,避免SCC的节点提前出栈

#include<vector> 
using namespace std;

const int N = 10050;
vector<int> e[N];

int dfn[N], low[N], tot;//时间戳,追溯值,时间戳计数器 
int stk[N], instk[N], top;//栈 , 
int scc[N], siz[N], cnt;//强连通分量,编号 

void tarjan(int x) {
	//进入一个点,操作如下 
	//#1.入 x时,盖戳,入栈
	dfn[x] = low[x] = ++tot;
	stk[++top] = x, instk[x] = 1;
	//#2。枚举x的邻点,分为三种情况
	//###2.1若y尚未访问,对y深搜,用low[y]更新low[x],
	//#####x是y的父节点,y能访问的点,父节点x一定也能访问到
	//###2.2 若y已经访问并且在栈中,
	//######说明y是祖先节点,或者是左子树节点,用dfn[y]更新Low[x]
	//###2.3 若y已经访问并且不在栈中,说明y已经搜索完毕
	//######其所在连通分量已经被处理,不需要对y进行操作
	int ex_size = e[x].size();
	vector<int> ex = e[x];
	for (int j = 0; j < ex_size; j++) {
		int y = ex[j];
		if (!dfn[y]) {
			//case y尚未访问
			tarjan(y);
			//返回x调用函数,子节点y的low值更新父节点x的low值 
			low[x] = min(low[x], low[y]);
		}
		else if (instk[y])//y访问且在栈中 
			low[x] = min(low[x], dfn[y]);
	}
	//#3.离x时,记录SCC,只有遍历了一个SCC,才可以对其出栈
	//#####更新low值的意义,避免SCC的节点提前出栈
	if (dfn[x] == low[x]) {
		//x是SCC的根
		int y; ++cnt;
		do {
			y = stk[top--];
			instk[y] = 0;
			scc[y] = cnt;//SCC编号 
			++siz[cnt];//SCC大小 
		} while (y != x);
	}

}
int main() {
	int n, m;
	scanf("%d %d", &n, &m);
	int u, v;

	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &u, &v);
		e[u].push_back(v);
	}
	for (int start = 1; start <= n; start++) {
		if(!dfn[start])tarjan(start);
	}
	
	//tarjan(1);
	int ans = 0;
	for (int i = 1; i <= cnt; i++) {
		if (siz[i] > 1)
			ans++;
	}
	printf("%d\n", ans);
	return 0;
}

# 【模板】最近公共祖先(LCA)

## 题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

## 输入格式

第一行包含三个正整数 $N,M,S$,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 $N-1$ 行每行包含两个正整数 $x, y$,表示 $x$ 结点和 $y$ 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 $M$ 行每行包含两个正整数 $a, b$,表示询问 $a$ 结点和 $b$ 结点的最近公共祖先。

## 输出格式

输出包含 $M$ 行,每行包含一个正整数,依次为每一个询问的结果。

## 样例 #1

### 样例输入 #1

```
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
```

### 样例输出 #1

```
4
4
1
4
4
```

## 提示

对于 $30\%$ 的数据,$N\leq 10$,$M\leq 10$。

对于 $70\%$ 的数据,$N\leq 10000$,$M\leq 10000$。

对于 $100\%$ 的数据,$1 \leq N,M\leq 500000$,$1 \leq x, y,a ,b \leq N$,**不保证** $a \neq b$。


样例说明:

该树结构如下:

 ![](https://cdn.luogu.com.cn/upload/pic/2282.png) 

第一次询问:$2, 4$ 的最近公共祖先,故为 $4$。

第二次询问:$3, 2$ 的最近公共祖先,故为 $4$。

第三次询问:$3, 5$ 的最近公共祖先,故为 $1$。

第四次询问:$1, 2$ 的最近公共祖先,故为 $4$。

第五次询问:$4, 5$ 的最近公共祖先,故为 $4$。

故输出依次为 $4, 4, 1, 4, 4$。


2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。

倍增算法:

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N = 500050;

vector<int> e[N];
int dep[N], fa[N][20];
void dfs_bz(int u, int father) {
	dep[u] = dep[father] + 1;
	//向上跳1,2,4.。。步的祖先
	fa[u][0] = father;
	for (int i = 1; i <= 19; i++) {
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	int eu_zise = e[u].size();
	for (int i = 0; i < eu_zise; i++) {
		int v = e[u][i];
		if (v != father)dfs_bz(v, u);
	}

}
int lca_bz(int u, int v) {
	if (dep[u] < dep[v])swap(u, v);
	//先跳到同一层 
	for (int i = 19; i >= 0; i--) {
		if (dep[fa[u][i]] >= dep[v])
			u = fa[u][i];
	}
	if (u == v)return v;
	//再跳到lca的下一层
	for (int i = 19; i >= 0; i--) {
		if (fa[fa[u][i]] != fa[fa[v][i]]) {
			u = fa[u][i], v = fa[v][i];
		}
	}
	return fa[u][0];
}
int main() {
	int n, m, s;
	scanf("%d %d %d", &n, &m, &s);
	int x, y;
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		e[x].push_back(y);
		e[y].push_back(x);
	}

	dfs_bz(s, 0);
	int a, b;
	int ans;
	for (int i = 0; i < m; i++) {
		scanf("%d %d", &a, &b);
		if (a == b)printf("%d\n", a);
		else {
			ans = lca_bz(a, b);
			printf("%d\n", ans);
		}

	}


	return 0;
}

tarjan算法

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N = 500050,M=2*N;

vector<int> e[N];
vector<pair<int,int> > query[N];
int fa[N],vis[N],ans[M];
int find(int u ){
	if(u==fa[u])return u;
	else{
		return fa[u]=find(fa[u]);
	}
}
void tarjan_lca(int u){
	//入u标记u 
	vis[u]=true;
	int eu_size=e[u].size();
	for(int i = 0;i <eu_size;i++){
		int v=e[u][i];
		if(!vis[v]){
			tarjan_lca(v);
			fa[v]=u;//回u时,v指向u 
		}
	} 
	//离u时,枚举LCA
	int queryu_size=query[u].size();
	pair<int ,int> q;
	for(int i = 0; i < queryu_size;i++) {
		q=query[u][i];
		int y=q.first,j=q.second;
		if(vis[y])ans[j]=find(y);
	}
	
}
int main() {
	int n, m, s;
	scanf("%d %d %d", &n, &m, &s);
	int x, y;
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	int a, b;
	for(int i = 1;i <= m;i++){
		scanf("%d %d", &a, &b);
		query[a].push_back({b,i});
		query[b].push_back({a,i});
	}
	for(int i = 1; i <= N;i++)fa[i]=i;
	tarjan_lca(s);
	
	for (int i = 1; i <= m; i++) {
			printf("%d\n", ans[i]);
	}


	return 0;
}

树上剖分 

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N = 500050;

vector<int> e[N];
int fa[N],dep[N],siz[N],hson[N];
int top[N];
void dfs1(int u,int father){
	//u是节点,father是u的父节点 
	//入u时 
	fa[u]=father,dep[u]=dep[father]+1,siz[u]=1;
	//
	int eu_size=e[u].size();
	int v ;
	for(int i = 0;i < eu_size;i++){
		v=e[u][i];
		if (v==father)continue;
		dfs1(v,u);
		siz[u]=siz[u]+siz[v];//出u时 
		if(siz[hson[u]]<siz[v])hson[u]=v;
	} 
}
void dfs2(int u,int t){
	//u是节点,t是u所在节点上的重链顶部节点
	top[u]=t;//入u时 
	if (!hson[u]) return;//没有重儿子,退出 
	dfs2(hson[u],t);//搜重儿子 
	int eu_size=e[u].size();
	int v ;
	for(int i = 0;i < eu_size;i++){
		v = e[u][i];
		if(v==fa[u] || v== hson[u])continue;
		dfs2(v,v);//搜轻儿子 
	} 
	
}


int lca_dfs12(int u,int v){
	//u和v是带查询lca的两个节点
	//保证u所在重链顶部节点深度更大
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
} 
int main() {
	int n, m, s;
	scanf("%d %d %d", &n, &m, &s);
	int x, y;
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs1(s,0);
	dfs2(s,s);
	int a, b;
	for(int i = 1;i <= m;i++){
		scanf("%d %d", &a, &b);
		if(a==b)printf("%d\n", a);
		else
		printf("%d\n", lca_dfs12(a,b));
	}
	return 0;
}

# 【模板】最小生成树

## 题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 `orz`。

## 输入格式

第一行包含两个整数 $N,M$,表示该图共有 $N$ 个结点和 $M$ 条无向边。

接下来 $M$ 行每行包含三个整数 $X_i,Y_i,Z_i$,表示有一条长度为 $Z_i$ 的无向边连接结点 $X_i,Y_i$。

## 输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 `orz`。

## 样例 #1

### 样例输入 #1

```
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
```

### 样例输出 #1

```
7
```

## 提示

数据规模:

对于 $20\%$ 的数据,$N\le 5$,$M\le 20$。

对于 $40\%$ 的数据,$N\le 50$,$M\le 2500$。

对于 $70\%$ 的数据,$N\le 500$,$M\le 10^4$。

对于 $100\%$ 的数据:$1\le N\le 5000$,$1\le M\le 2\times 10^5$,$1\le Z_i \le 10^4$。


样例解释:

 ![](https://cdn.luogu.com.cn/upload/pic/2259.png) 

所以最小生成树的总边权为 $2+2+3=7$。

kruskal

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N=5050; 
const int M=400050; 
//加边法 
struct edge{
	int u,v,w;
	bool operator<(const edge &t)const{
	return w<t.w;}
} e[M];
int n,m;
int fa[N],ans,cnt;
int find(int x){
	if(fa[x]==x)return x;
	return fa[x]=find(fa[x]);
}
bool kruskal(){
	//mlogm
	sort(e,e+m);//所有边按照边权从小到大排序 
	for(int i = 1;i <= n;i++)fa[i]=i;//初始化并查集 
	//按顺序枚举边
	边连接的两个点不在同一个集合,把该边加入最小生成树合并集合
	在同一集合,跳过 
	for(int i = 0; i < m;i++){
		int x=find(e[i].u);//点u所在集合 
		int y=find(e[i].v);//点v所在集合
		if(x!=y){
			fa[x]=y;
			ans+=e[i].w;
			cnt++;
		}
	}
	return cnt==n-1;
}
int main(){
	scanf("%d %d",&n,&m);
	int x,y,weight;
	for(int i = 0;i < m;i++){
		scanf("%d %d %d",&x,&y,&weight);
		e[i]=edge{x,y,weight};
	}
	if(kruskal())printf("%d\n",ans);
	else printf("orz\n");
	
	return 0;
}

prim

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N=5050; 
const int M=400050; 
struct edge{int v,w;};
vector<edge> e[N];
int d[N],vis[N];
int n,m,ans,cnt;
//e[u] u的所有邻边的终点和边权
//d[u] u点与圈外邻点的最小距离,vis[u]标记u是否出圈 
//选取距离最小的点出圈,直到圈内为空 
bool prim(int s){
	for(int i = 0;i <= n;i++)d[i]=1<<30;
	d[s]=0;
	for(int i = 1;i <= n;i++){
		int u=0;
		//选择点u 
		for(int j=1; j<=n;j++)
			if(!vis[j]&&d[j]<d[u])u=j;
			
			vis[u]=1;
			ans=ans+d[u];
			if(d[u]!=1<<30)cnt++;
			int eu_size=e[u].size();
			//更新d数组 
			for(int k = 0;k <eu_size;k++){
				int v=e[u][k].v,w=e[u][k].w;
				if(d[v]>w)d[v]=w;
			}
		
	}
	return cnt==n;
}


int main(){
	 
	scanf("%d %d",&n,&m);
	int x,y,weight;
	for(int i = 0;i < m;i++){
		scanf("%d %d %d",&x,&y,&weight);
		e[x].push_back({y,weight});
		e[y].push_back({x,weight});
	}
	if(prim(1))printf("%d\n",ans);
	else printf("orz\n");
	
	return 0;
}

堆优化

#include<bits/stdc++.h>
#include<iostream>
#include<vector>

using namespace std;
const int N=5050; 
const int M=400050; 
struct edge{int v,w;};
vector<edge> e[N];
int d[N],vis[N];
int n,m,ans,cnt;
priority_queue<pair<int,int> >q;

//e[u] u的所有邻边的终点和边权
//d[u] u点与圈外邻点的最小距离,vis[u]标记u是否出圈 
//选取距离最小的点出圈,直到圈内为空 
bool prim(int s){
	for(int i = 0;i <= n;i++)d[i]=1<<30;
	d[s]=0;
	q.push({0,s});
	while(q.size()){
		int u=q.top().second;q.pop();
		if(vis[u])continue;//再出队跳过 
		vis[u]=1;//标记u已经出队 
		ans+=d[u];
		cnt++;
		int eu_size=e[u].size(); 
		//更新d数组 
		for(int k = 0;k <eu_size;k++){
			int v=e[u][k].v,w=e[u][k].w;
			if(d[v]>w){
				d[v]=w;
				q.push({-d[v],v});//大根堆 
			}
		}
	}
	return cnt==n;
}


int main(){
	 
	scanf("%d %d",&n,&m);
	int x,y,weight;
	for(int i = 0;i < m;i++){
		scanf("%d %d %d",&x,&y,&weight);
		e[x].push_back({y,weight});
		e[y].push_back({x,weight});
	}
	if(prim(1))printf("%d\n",ans);
	else printf("orz\n");
	
	return 0;
}

tri模板题---05.09

给定 NN 个字符串 S1,S2…SNS1,S2…SN,接下来进行 MM 次询问,每次询问给定一个字符串 TT,求 S1∼SNS1∼SN 中有多少个字符串是 TT 的前缀。

输入字符串的总长度不超过 106106,仅包含小写字母。

输入格式
第一行输入两个整数 N,MN,M。

接下来 NN 行每行输入一个字符串 SiSi。

接下来 MM 行每行一个字符串 TT 用以询问。

输出格式
对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围
1≤N,M≤1051≤N,M≤105
输入样例:
3 2
ab
bc
abc
abc
efg
输出样例:
2
0

#include<bits/stdc++.h>
using namespace std;

const int N=1000010,M=500000;

int n,m;
//儿子数组ch[N][26]
//计数数组cnt[N]
//节点编号idx 
int ch[N][26],cnt[N],idx;
char s[N];

void insert(char *s){
	int p=0;
	for(int i = 0; s[i];i++){
		int j=s[i]-'a';//字母映射 
		if(!ch[p][j])ch[p][j]=++idx;
		p=ch[p][j];
	}
	cnt[p]++;//插入次数 
}
int query_word(char *s){
	int p=0;
	for(int i = 0;s[i];i++){
		int j=s[i]-'a';
		if(!ch[p][j])return 0;
		p=ch[p][j];
	}
	return cnt[p];
}
int query(char *s){
	int p=0,res=0;
	for(int i = 0;s[i];i++){
		int j=s[i]-'a';
		if(!ch[p][j])break;
		p=ch[p][j];
		res=res+cnt[p];
	}
	return res;
}
int main(){
	
	scanf("%d%d",&n,&m);
	
	while(n--){
		scanf("%s",s);
		insert(s);
	} 
	while(m--){
		scanf("%s",s);
		printf("%d\n",query(s));
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值