WEEK 4 周报

news2024/12/26 11:01:58

这周专题是图论:

目录

这周专题是图论:

树和图的存储及遍历:

存储:

遍历:(每个点只遍历一次,所以需要卡一个布尔数组标记是否已经被遍历)

1.深度优先遍历:

树的重心:

2.宽度优先遍历:

图中点的层次:

拓扑序列(有向图才会有拓扑序列):

 最短路:

 朴素版Dijkstra:

堆优化版本的Dijkstra;

Bellman-Ford算法: 



树和图的存储及遍历:

树是特殊的图:无环且连通,所以只讲图;

图分为有向图和无向图,可以把无向图看成特殊有向图;

有向图如何存储:

存储:

一般有两种存储方式:

  • 邻接矩阵g[a][b],如果有权重就将权重赋值给数组,如果没有权重就是布尔值,这条边存不存在。(不能存储重边)
  • 邻接表(用的最多):每个点都有一个单链表,存储从这个点走向那个点。

举例:

当我们想要插入一条新的边:

比如新加一条2到3;

找到2的单链表然后把3插到里面去:

一般选在头的位置插

#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=N*2;
int h[N];//N个链表的链表头
int e[M];//存的所有的边
int ne[M];//每一个节点的next节点;
int idx;
void add(int a,int b){//将节点b插入到节点a,比如新增边:2-3;
	e[idx]=b;//将节点3的值附上
	ne[idx]=h[a];//看图就是绿色的边
	h[a]=idx++;//看图是红色的边
	//idx表示当前节点已经用了多少个节点
	//或者说可以理解成每个节点分配的唯一标识序号
}
int main(){
	memset(h,-1,sizeof(h));
	
	return 0;
}

 我觉得这里的idx就是建立一个新的“节点”,将要插入的点赋值给这个新节点,然后将这个节点的下一个值就是ne[idx]接到要插入的节点的头节点的第一个值,就是绿色边,然后再将被插入节点的头节点接到 插入进来的节点,就是下图的红色边。然后为了下一次新边的加入,就预先再创造一个新节点等待赋值。

遍历:(每个点只遍历一次,所以需要卡一个布尔数组标记是否已经被遍历)

时间复杂度是O(n+m);

1.深度优先遍历:

#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=N*2;
int h[N];//N个链表的链表头
int e[M];//存的所有的边
int ne[M];//每一个节点的next节点;
int idx;
bool st[N];
void add(int a,int b){//将节点b插入到节点a,比如新增边:2-3;
	e[idx]=b;//将节点3的值附上
	ne[idx]=h[a];//看图就是绿色的边
	h[a]=idx++;//看图是红色的边
}
void dfs(int u){
	st[u]=1;//标记一下这个点已经被搜索过了。
	for(int i=h[u];i!=-1;i=ne[i])//遍历一下u的所有出边
	{
		int j=e[i];
		if(!st[j])dfs(j);
	}
}
int main(){
	memset(h,-1,sizeof(h));
	
	dfs(1);
	return 0;
}

树的重心:

定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中的最大值最小,那么这个节点被称为树的重心。

(树的重心可能不唯一)

举个例子:

删掉1节点,连通块的最大值是4

删掉2的连通块的最大值是6

 删掉4,连通块最大值是5

其余的边就不枚举了

所以树的重心是1,最优解是4

846. 树的重心 - AcWing题库

用树的深度优先遍历。

怎么算?以删除4为例子:

 

#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=N*2;
int h[N];//N个链表的链表头
int e[M];//存的所有的边
int ne[M];//每一个节点的next节点;
int idx;
int n,s,ans=N;
bool st[N];
void add(int a,int b){//将节点b插入到节点a,比如新增边:2-3;
	e[idx]=b;//将节点3的值附上
	ne[idx]=h[a];//看图就是绿色的边
	h[a]=idx++;//看图是红色的边
}
//返回
int dfs(int u){//以u为根的子树的大小
	st[u]=1;//标记一下这个点已经被搜索过了。
	int sum=1,res=0;
	for(int i=h[u];i!=-1;i=ne[i])//遍历一下u的所有出边
	{
		int j=e[i];
		if(!st[j]){
			int s=dfs(j);//当前子树的大小,也算一个连通块
			res=max(res,s);//当前以儿子为根节点的子树是以u为根节点的子树的一部分
			sum+=s;
		}
	}
	res=max(res,n-sum);
	ans=min(ans,res);
	return sum;
}
int main(){
	
	cin>>n;
	memset(h,-1,sizeof(h));
	for(int i=0;i<n-1;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	dfs(1);
	cout<<ans<<endl;
	return 0;
}

2.宽度优先遍历:

 847. 图中点的层次 - AcWing题库

图中点的层次:

#include<bits/stdc++.h>
using namespace std;
const int N=100005,M=2*N;
int e[M],ne[M],h[N],idx;
int d[M],q[N],n,m;
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
int bfs(){
	int hh=0,tt=0;//队头和队尾
	q[0]=1;
	memset(d,-1,sizeof(d));
	d[1]=0;//最开始只有第一个被遍历过了
	while(hh<=tt){
		int t=q[hh++];
		for(int i=h[t];i!=-1;i=ne[i]){
			int j=e[i];
			if(d[j]==-1){
				d[j]=d[t]+1;
				q[++tt]=j;//数组模拟队列的写法
			}
		}
	}
	return d[n];
}
signed main()
{
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
	
	}
	cout<<bfs()<<endl;
	return 0;
}

 图的宽搜遍历的经典应用:

拓扑序列(有向图才会有拓扑序列):

AcWing 848. 有向图的拓扑序列 - AcWing

什么是拓扑序列:对于图中的每条边 (x,y),x 在 A中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

举例:

 所有的边都是从前指向后的

一个有向无环图一定存在一个拓扑序列,所以一个有向无环图也可以称为一个拓扑图

所有入度为0的点都可以排在最前面的位置,(一个无环图至少存在一个入度为0的点)

后面就是一个宽搜的过程

#include<bits/stdc++.h>
using namespace std;
const int N=100005,M=2*N;
int e[M],ne[M],h[N],idx;
int d[M],q[N],n,m;
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
bool topsort(){
	int hh=0,tt=-1;
	for(int i=1;i<=n;i++){
		if(!d[i]){
			q[++tt]=i;
		}
		
	}
	while(hh<=tt){
		int t=q[hh++];
		for(int i=h[t];i!=-1;i=ne[i]){
			int j=e[i];
			d[j]--;
			if(d[j]==0)q[++tt]=j;
		}
	}
	return tt==n-1;
}
signed main()
{
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
	    d[b]++;
	}
	if(topsort()){
		for(int i=0;i<n;i++){
			cout<<q[i]<<" ";
		}
	}
	else cout<<"-1";
	return 0;
}

 最短路:

难点在建图:

 朴素版Dijkstra:

AcWing 849. Dijkstra求最短路 I - AcWing

#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,m;
//cin>>n>>m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra(){
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=0;i<n;i++){//遍历所有的点
		int t=-1;//表示还没有确定
		for(int j=1;j<=n;j++){//当前没有确定最短路点当中距离最小的那个点
			if(!st[j]&&(t==-1||dist[t]>dist[j])){//或者当前的t不是最短的;
				t=j;
				
			}
		}
		st[t]=1;
		for(int j=1;j<=n;j++){
			dist[j]=min(dist[j],dist[t]+g[t][j]);
			
		}
	}
	
	if(dist[n]==0x3f3f3f3f)return -1;
	return dist[n];
}
int main(){
	scanf("%d%d",&n,&m);
	memset(g,0x3f,sizeof(g));
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c);
	}
	//g[a][b]=min(g[a][b],c);
	int t=dijkstra();
	printf("%d\n",t);
	return 0;
}

稠密图用邻接矩阵,

稀疏图用邻接表;

注意:

在这里数组初始化用memset特别要注意:

给距离初始化的时候;

当我们用0x3f给距离数组赋值的时候,赋值的结果却是0x3f3f3f3f

memset函数初始化特点是按字节去逐个初始化 

所以,一般只用来初始化0,-1,0x3f这几个数字。其他的则建议使用for循环来初始化。

其他数字转变的过程如下图:

 如果将数组初始化为正无穷的时候,用到了memset,要用0x3f,最后如果验证时候为正无穷的时候要等于的是0x3f3f3f3f;

注意用memset初始化为正无穷的时候,不能用define int long long;

d[n]:            4557430888798830399

0x3f3f3f3f:   1061109567

堆优化版本的Dijkstra;

用优先队列找到距离最近的点

AcWing 850. Dijkstra求最短路 II - AcWing

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
vector<PII>mp[150005];
int n,m;
int dist[150001];
bool st[155001];
void djstl(){
	for (int i = 1; i <=n ; ++i) {//举例数组初始化为无穷大
		dist[i]=(int)1e15;
	}
	priority_queue<PII,vector<PII>,greater<PII>>q;//定义优先队列
	dist[1]=0;//起点的距离是0
	q.push({0,1});//将起点放入队列中,要对距离排序,所以距离在第一位
	while(!q.empty()){//如果队列不为空
		auto t=q.top();//将距离最小的点弹出
		q.pop();
		int ver=t.second,ds=t.first;//最上面的点标号是ver,距离是ds;
		if(st[ver])continue;//如果该点已经确定了最短路,就继续找下一个最近的点
		st[ver]= true;//标记
		for (int i = 0; i <mp[ver].size() ; ++i){//找以ver为出边所连接的点
			auto p=mp[ver][i];//枚举所有他所连接的点;
			if(dist[p.first]>ds+p.second){//如果1到该点的距离大于(已经确定最短路的点到该点的距离加上最短路的距离)
				dist[p.first]=ds+p.second;//更新该点的最短距离
				q.push({dist[p.first],p.first});//将已经找到最短路的点放入队列中;
			}
		}
	}
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for (int i = 0; i <m ; ++i) {
		int x,y,z;
		cin>>x>>y>>z;
		mp[x].push_back({y,z});//读入边
	}
	djstl();//进行最短路
	if(dist[n]==(int)1e15)cout<<"-1";
	else
		cout<<dist[n];
	return 0;
	
}

 例题:旅行

1004-旅行_2021秋季算法入门班第九章习题:图论(重现赛)@IR101 (nowcoder.com)

 有n个点,选择3个点让这三个点之间最短路径最大:

样例1:答案是422

 样例二:答案是三

答案是0 

样例三:

样例4:答案是九

稀疏图,我们可以用邻接表存图或则链式向前星

然后我们计算以每个点为起点的最短路,将起点作为我们要选取的三个点中的中间点,以该起点,找到对应的三点中作为起点和终点的点。

简单说就是枚举每个点当作中间点,求出由当前点到其他任意两点最长的两个路径。

是稀疏图且是正边,那么我们可以使用堆优化版的Dijkstra,

对每一个点跑一遍Dijkstra

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

// 定义常量  
const int N = 1005, INF = 0x3f3f3f3f;  

// 定义边和节点的结构体,用于Dijkstra算法中的优先队列  
struct node{  
	int x, d; // x是节点编号,d是从起点到该节点的距离  
	bool operator< (const node& a) const // 重载小于运算符,使得优先队列按照距离d的降序排列  
	{  
		return d > a.d;  
	}  
};  

// 用于存储最短路径的数组  
int dis[N];  

// 图的邻接表表示  
int Next[2*N], e[2*N], head[N], val[2*N]; // 因为无向图,每条边存储两次  
int fg[N] = {0}; // 标记数组,用于Dijkstra算法中判断节点是否已经被访问过  

int n, m, cnt = 0; // n是节点数,m是边数,cnt用于记录边的数量  

// 添加边到图的邻接表中  
void add(int x, int y, int z)  
{  
	e[cnt] = y;  
	val[cnt] = z;  
	Next[cnt] = head[x];  
	head[x] = cnt++;  
}  

// Dijkstra算法实现,用于计算从x出发到所有其他节点的最短路径  
int Dijkstra(int x)  
{  
	priority_queue<node> q; // 使用优先队列优化Dijkstra算法  
	memset(dis, INF, sizeof(dis)); // 初始化距离数组为正无穷  
	memset(fg, 0, sizeof(fg)); // 初始化访问标记数组  
	dis[x] = 0; // 起点到自身的距离为0  
	q.push({x, 0}); // 将起点加入优先队列  
	
	while(!q.empty())  
	{  
		node k = q.top();  
		q.pop();  
		if(fg[k.x]) continue; // 如果节点已经被访问过,则跳过  
		fg[k.x] = 1; // 标记节点为已访问  
		
		// 遍历当前节点的所有邻接节点  
		for(int i=head[k.x]; i!=-1; i=Next[i])  
		{  
			if(dis[e[i]]>dis[k.x]+val[i]) // 如果找到更短的路径  
			{  
				dis[e[i]] = dis[k.x] + val[i]; // 更新距离  
				q.push({e[i], dis[e[i]]}); // 将邻接节点加入优先队列  
			}  
		}  
	}  
	
	// 对所有可达节点的距离进行降序排序,找到最远的两个距离  
	sort(dis+1, dis+n+1, greater<int>());  
	int kk = 0, sum = 0;  
	for(int i=1; i<n && kk<2; i++) // 遍历排序后的距离数组,找到前两个非无穷大的距离  
		if(dis[i]!=INF) sum += dis[i], kk++;  
	
	return (kk<2)? -1:sum; // 如果找不到两个可达的节点,则返回-1;否则返回两个最远距离的和  
}  

int main()  
{  
	ios::sync_with_stdio(false);  
	cin.tie(0), cout.tie(0);  
	int t;  
	cin >> t; // 读取测试用例数量  
	
	while(t--)  
	{  
		int maxv = -1; // 初始化最大值为-1  
		cnt = 0; // 重置边的计数器  
		memset(head, -1, sizeof(head)); // 初始化邻接表头指针  
		// 注意:这里不应该重置e, val, Next数组,因为它们会被连续使用  
		
		cin >> n >> m; // 读取节点数和边数  
		
		// 读取边并添加到图中  
		for(int i=0; i<m; i++)  
		{  
			int x, y, z;  
			cin >> x >> y >> z;  
			add(x, y, z);  
			add(y, x, z); // 因为是无向图,所以边要添加两次  
		}  
		
		// 枚举每个节点作为中转点,计算最大距离和  
	for(int i=1; i<=n; i++) maxv = max(maxv, Dijkstra(i)); //逐一枚举中转点 
		cout << maxv << endl;
	}
	return 0;
}

 时间复杂度是n*mlogn

Bellman-Ford算法: 

适用有负权边的图,限制边数;

如果有负权回路,最短路不一定存在,最短路可能为负无穷;

所以一般情况下:有最短路就不会存在负权回路

853. 有边数限制的最短路 - AcWing题库

 迭代n次,每一次循环所有边(a,b,w 从a到b权重是w);

迭代k次,表示从一号点经过不超过k条边走到每个点的最短距离); 

这个算法存边随便存,一般定义一个结构体数组

struct nod{

int a,b,w;

}edge[M];

遍历更新dist[b]=min(dist[b],dist[a]+w);

循环遍历n次后,对于所有的边:

dist[b]<=dist[a]+w;(三角不等式)

更新的过程叫:松弛操作;  

	memcpy(backup,dist,sizeof(dist));

 这个是把dist数组备份一下;

举一个例子:

枚举所有边的时候可能发生串联

会无视边的限制

保证更新点时候时候是上一次迭代的结果,就得给dist数组备份;所以backup数组存的就是上一次迭代的结果

	if(dist[n]>0x3f3f3f3f/2){
		flag=1;
	}

这里为什么大于一个0x3f3f3f3f/2?;

因为一号点到不了n号结点,但是中间的某个结点能到达n;

但是此时中间的某个节点是0x3f3f3f3f,在走到终点的时候可能走的都是负权边,但是题目有限制不会减掉太多,所以最后的答案必定小于0x3f3f3f3f,但是此时却也是到达不了的情况,所以做了除以2的处理

#include<bits/stdc++.h>
using namespace std;
const int M=100005;
int n,m,k,flag;
struct Edge{
	int a,b,w;	
}edge[M];
int dist[505];
int backup[M];
int bellman_ford(){
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=0;i<k;i++){//不超过k条边的最短路径
		memcpy(backup,dist,sizeof(dist));
		for(int j=0;j<m;j++){
			int a=edge[j].a,b=edge[j].b,w=edge[j].w;
			dist[b]=min(dist[b],backup[a]+w);
		}
	}
	if(dist[n]>0x3f3f3f3f/2){
		flag=1;
	}
	
	return dist[n];
}
signed main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<m;i++){
		int a,b,w;
		cin>>a>>b>>w;
		edge[i]={a,b,w};
	}
	int t=bellman_ford();
	if(flag)puts("impossible");
	else cout<<t<<endl;
	return 0;
}

 SPFA算法:

最短路

是对Bellman-Ford的一种优化;

要想更新dist[b],那么就得dist[a]变小;

用宽搜来做优化

 将起点放入队列;

只要队列不为空:(只要队列里边还有节点变小的话)

1.将队头取出;t=q.front();q.pop();

2.更新t的所有出边;(如果t变小,所有以t为起点的终点都有可能变小;) t -_w_->b;

3.将b放入队列;

#include<bits/stdc++.h>
using namespace std;
const int M=100005;
int h[M];
int e[M];
int w[M];
int idx;
int f;
int ne[M];
int dist[M];
int st[M];
void add(int a,int b,int ww){
	e[idx]=b;
	w[idx]=ww;
	ne[idx]=h[a];
	h[a]=idx++;	
}
void spfa(){
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	queue<int>q;
	q.push(1);
	st[1]=1;//标记起点已经被访问过了
	while(!q.empty()){
		auto p=q.front();
		q.pop();
		st[p]=0;//表示这个点已经不在队列里了
		for(int i=h[p];i!=-1;i=ne[i]){//访问以p点为起点的终点
			int j=e[i];//去点
			if(dist[j]>(dist[p]+w[i])){//如果起点的距离加上边权小于终点距离就更新他;
				dist[j]=dist[p]+w[i];
				if(!st[j]){//如果该终点还没有被更新过,放入队列并且标记为以被更新过;
					q.push(j);
					st[j]=1;
				}
			}
			
		}
		
	}
}

signed main(){
	int n,m;
	cin>>n>>m;
	memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++){
		int a,b,ww;
		cin>>a>>b>>ww;
		add(a,b,ww);
	}
	spfa();
	if(dist[n]==0x3f3f3f3f)cout<<"impossible"<<endl;
	else cout<<dist[n];
	
	return 0;
} 

感觉代码形式和dijkstra算法很像,那他们的区别在哪里呢?

dijkstra算法:

它是每次找到离原点最近的点,放入堆中(成为堆顶)并且标记,再以这个点为起点去更新与它相连的点,

SPFA算法:

它是要对所有的边去进行一次松弛操作;

进行了n-1次更新,先初始化dis数组,起点赋值为0,其余赋值为无穷大;

先起点入队列,入了队列的被标记,当队列不为空时循环,队首元素出队,松弛与队首元素相连的边,这些被更新的点如果不在队列中就加入队列, 再次队首元素出队;

松弛与队首元素相连的边,它是不需要去找离原点最近的点的;

所以Dijkstra算法用的是小根堆优化;

SPFA直接用的队列;

判断是否有负环

852. spfa判断负环 - AcWing题库

判断负环

就是spfa最短路多加了一个cnt[M]数组;

要将所有点都入队,因为不是每个点都能到达负环,所以得从每一个点都出发一遍

然后就是dist初始化可以删掉,详细细节见代码:

#include<bits/stdc++.h>
using namespace std;
const int M=100005;
int h[M];
int e[M];
int w[M];
int idx;
int cnt[M];
int f;	
int n,m;
int ne[M];
int dist[M];
int st[M];
void add(int a,int b,int ww){
	e[idx]=b;
	w[idx]=ww;
	ne[idx]=h[a];
	h[a]=idx++;	
}
bool spfa(){
	
	queue<int>q;
	for(int i=1;i<=n;i++){
		q.push(i);
	}
	while(!q.empty()){
		auto p=q.front();
		q.pop();
		st[p]=0;//表示这个点已经不在队列里了
		for(int i=h[p];i!=-1;i=ne[i]){//访问以p点为起点的终点
			int j=e[i];//去点
			if(dist[j]>(dist[p]+w[i])){//如果起点的距离加上边权小于终点距离就更新他;
				dist[j]=dist[p]+w[i];
				cnt[j]=cnt[p]+1;
				if(cnt[j]>=n)return 1;
				if(!st[j]){//如果该终点还没有被更新过,放入队列并且标记为以被更新过;
					q.push(j);
					st[j]=1;
				}
			}
			
		}
		
	}
	return 0;
}

signed main(){

	cin>>n>>m;
	memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++){
		int a,b,ww;
		cin>>a>>b>>ww;
		add(a,b,ww);
	}
	//spfa();
	if(spfa())cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return 0;
} 

Flord算法:

有三重循环,

for(int k=1;k<=n;k++){
		for(int i=1;i<n;i++){
			for(int j=1;j<=n;j++){
				d[i][k]=min(d[i][j],d[i][k]+d[k][j]);
			}
		}
	}

 循环结束之后就是d[i][j]就是从  i 到 j 的最短路;

#include<bits/stdc++.h>
using namespace std;
int d[210][210];
int i,j,v,w,u;
int n,m,k;

void floyd(){
    
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++)
            d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
        }
    }
}

signed main(){
    cin>>n>>m>>k;
    memset(d,0x3f,sizeof d);
    for(i=1;i<=n;i++) d[i][i]=0;
    
    while(m--){
        cin>>u>>v>>w;
        d[u][v]=min(d[u][v],w);
    }
    floyd();
    
    while(k--){
        int x,y;
        cin>>x>>y;
        if(d[x][y]>0x3f3f3f3f/2) cout<<"impossible";
        else cout<<d[x][y];
        cout<<'\n';
    }
}

常用代码模板3——搜索与图论 - AcWing

最小生成树

最小生成树对应的图都是无向图

 朴素版prim:

858. Prim算法求最小生成树 - AcWing题库

1.初始化距离dist[N];将所有距离初始化为正无穷

2.n次迭代,每次迭代:

a.在当前连通块当中的所有点——>s;

找到集合外距离最近的点,赋值给t。

用t更新其他点到集合的距离

把t加到集合当中去(s[t]=1);

#include <iostream>
#include <cstring>

using namespace std;

const int N=510; 

bool st[N]; //将生成树看成一个集合,在集合中即为true,不在即为false 
int dist[N],g[N][N]; //dist数组是点到该集合的距离,g数组是两点之间的距离 
int n,m,res;

int prim()
{
	memset(dist,0x3f,sizeof(dist));
	
	for(int i=0;i<n;i++) //因为最小生成树要加入所有点,故要遍历n次 
	{
		int t=-1;
		for(int j=1;j<=n;j++)
		{
			if(!st[j]&&(t==-1||dist[t]>dist[j])) //在未加入生成树的点集合中选择一个点 
			{									 //且该点距离集合的距离最小,符合最小生成树的边权之和最小的特性 
				t=j;
			}
		}
		
		st[t]=true; //选出点后便加入最小生成树集合 
		if(i&&dist[t]==0x3f3f3f3f) return dist[t]; //如果某点不是第一个加入最小生成树中的点,且该点的值为初始化的0x3f3f3f3f,即该点不能与其他点相连,故不能形成最小生成树 
		if(i) res+=dist[t]; //如果该点不是第一个点,则将该点与最小生成树集合之间的边的权重加入结果中 
		
		for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]); //每次最小生成树集合中加入新的点后,都更新其他点与集合的距离,本质上是更新未加入集合的点,但由于特判增加复杂度,故直接遍历所有点 
	}
	
	return res; //若未发现不与集合连通的点,则返回点之间的边权重之和 
}

int main()
{
	memset(g,0x3f,sizeof(g));
	
	cin>>n>>m;
	
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b]=g[b][a]=min(g[a][b],c);
	}
	
	int t=prim();
	
	if(t==0x3f3f3f3f) cout<<"impossible"<<endl;
	else cout<<t<<endl;
	
	return 0;
}

 Kruskal算法:

859. Kruskal算法求最小生成树 - AcWing题库

最开始的时候没有边

1.将所有边按权重从小到大排序O(mlogn);

2.枚举每条边a,b,权重是c。枚举的时候

3.如果当前的a,b不连通的话,将这条边加入集合中(并查集);

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+5,M=2e5+5,INF=0x3f3f3f3f,Mod=10007;
const double eps=1e-8;
typedef long long ll;

int n,m,d[N],fa[N];
struct E{
    int a,b,c;
    bool operator< (const E &W)const{
        return c<W.c;
    }
}ed[M];
int find(int x){
    if(fa[x]!=x)fa[x]=find(fa[x]);
    return fa[x];
}
int kruscal(){
    sort(ed,ed+m);
    for(int i=1;i<=n;++i)fa[i]=i;
    int res=0,cnt=0;
    for(int i=0;i<m;++i){
        int a=ed[i].a,b=ed[i].b,c=ed[i].c;
        a=find(a),b=find(b);
        if(a!=b){
            res+=c;
            fa[a]=b;
            cnt++;
        }
    }
    if(cnt<n-1)return INF;
    return res;
}
int main(){
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<m;++i){
        cin>>ed[i].a>>ed[i].b>>ed[i].c;
    }
    int res=kruscal();
    if(res==INF)cout<<"impossible";
    else cout<<res;
    return 0;
}

二分图

一个图数二分图当且仅当图中不含奇数环(环当中边的数量是奇数)

染色法

 由于图中不含奇数环所以染色过程中一定没有矛盾

如果有矛盾就不是二分图;

染色法从前往后遍历:

深度优先遍历;

if(i未被染色) dfs(i,颜色); 

AcWing 860. 染色法判定二分图 - AcWing

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+3;
int n,m;
int i,j,u,v;
int cl[N];
struct edge{
    int v;
};
vector<edge>ed[N];

bool dfs(int u,int c)
{
    cl[u]=c;
    for(int i=0;i<ed[u].size();i++)
    {
        int v=ed[u][i].v;
        if(!cl[v])
        {
            if(!dfs(v,3-c)) return false;
        }
        else if(cl[v]==c) return false;
    }
    return true;
}

signed main()
{
    cin>>n>>m;
    memset(cl,0,sizeof cl);
    while(m--)
    {
        cin>>u>>v;
        ed[u].push_back({v});
        ed[v].push_back({u});
    }
    bool f=true;
    for(i=1;i<=n;i++)
    {
        if(!cl[i])
        {
            if(!dfs(i,1)){
                f=false;
                break;
            }
        }
    }
    if(f) cout<<"Yes";
    else cout<<"No";
}

 匈牙利算法:

匈牙利算法:男女相亲,男选女可占可让,贪心配对:

1.枚举n个男生,

每轮vis[]初始化为0,(即女生皆可选),

深搜若能配成对,ans+1;

2.枚举男生u的心仪女孩v,

(1)若女孩已标记就逃过

(2)若女孩没男朋友,则配成对

若女孩的男友可以让出,则配成队。

(3)否则,枚举u的下一个心仪女孩

3.枚举完u的心仪女孩都不能配成对,则return 0;

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k,m,a,b,ans;
#define M 10000
#define N 200005
struct edge{
	int v,next;
}e[M];
int h[N],idx;
int vis[N],match[N];
void add(int a,int b){
	e[++idx]={b,h[a]};
	h[a]=idx;
}
bool dfs(int u){
	for(int i=h[u];i;i=e[i].next){
		int v=e[i].v;//枚举该男孩的心动女孩
		if(vis[v])continue;
		vis[v]=1;//先标记这个女孩
		if(!match[v]/*如果该女孩没有对象*/||/*枚举此女孩的男友还有没有可选的女孩*/dfs(match[v])){
			match[v]=u;//配成对
			return 1;
		}
	}
	return 0;
}
signed main(){
	cin>>n>>m>>k;
	for(int i=0;i<k;i++){
		cin>>a>>b;
		add(a,b);
	}
	for(int i=1;i<=n;i++){//男选女,枚举每一个男生
		memset(vis,0,sizeof(vis));
		if(dfs(i))ans++;//走进男生i;
	}
	cout<<ans<<endl;
}

“见面吗?

如果答案是不可以,

那我就等会再问一遍 (⊂(・▽・⊂)”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1984791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache POl初学

介绍 入门案例 通过POI写入操作 /*** 使用POI操作Excel文件*/ public class POITest {/*** 通过POI创建Excel文件并写入文件内容**/public static void write() throws Exception{//在内存中创建一个Excel文件XSSFWorkbook excel new XSSFWorkbook();//在Excel中创建一个Shee…

麦田物语第十九天

系列文章目录 麦田物语第十九天 文章目录 系列文章目录一、保存和加载场景中的物品二、设置鼠标指针根据物品调整 一、保存和加载场景中的物品 本小节我们想要解决一个问题&#xff0c;就是当我们跳转场景后&#xff0c;在返回之前场景&#xff0c;发现场景中被我们拾取的物品…

md文件转doc文件

目录 起因 实践 python方式安装 安装包安装 转换 后记 起因 近期需要提交一些文件出去&#xff0c;一般都是要word或pdf版的&#xff0c;但是手头只有md格式的&#xff0c;于是需要将md转为doc 实践 问了下度娘&#xff0c;pandoc是个不错的方法&#xff0c;可以通过下…

【C++】:错误处理机制 -- 异常

目录 前言一&#xff0c;C语言传统的处理错误的方式二&#xff0c;C异常的概念三&#xff0c;异常的使用3.1 异常的抛出和匹配原则3.2 在函数调用链中异常栈展开匹配原则3.3 异常的重新抛出3.4 异常规范 四&#xff0c;自定义异常体系五&#xff0c;异常的优缺点 点击跳转至文章…

经典⾯试题,循环中使⽤闭包解决 var 定义函数的问题

⾸先因为 setTimeout 是个异步函数&#xff0c;所有会先把循环全部执⾏完毕&#xff0c;这时候 i 就是 5了&#xff0c;所以会输出6个 5。 解决办法两种&#xff0c;第一种使用闭包 &#xff1a; 第⼆种就是使用 setTimeout 的第三个参数&#xff1a; 第三种就是使用 let 定义 …

Power功效分析之方差原理及案例教程

Power功效分析常用于实验研究时样本量的计算&#xff08;或功效值计算&#xff09;&#xff0c;实验研究中进行方差分析的情况较多&#xff0c;在SPSSAU中单独将方差放成一个计算Power的方法&#xff0c;其具体包括单因素方差/双因素方差和多因素方差&#xff0c;具体如下表格所…

Callable 与 Runnable:多线程编程中的两大接口对比

Callable 与 Runnable&#xff1a;多线程编程中的两大接口对比 1、主要区别1.1 返回值1.2 使用方式 2、适用场景3、示例 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java多线程编程中&#xff0c;Callable和Runnable是两个核心接口&am…

如何打造 BeatBuddy:一款分析你的 Spotify 数据的 Web 应用

欢迎来到雲闪世界&#xff01;我将解释我是如何构建 BeatBuddy 的&#xff0c;这是一款分析您在 Spotify 上收听内容的网络应用。受 Spotify Wrapped 的启发&#xff0c;它旨在解读您当前的心情并提供您可以根据该分析进行调整的建议。 如果你不想阅读所有内容&#xff0c;只想…

未授权访问漏洞系列

环境 1.此漏洞需要靶场vulhub&#xff0c;可自行前往gethub下载 2.需要虚拟机或云服务器等linux系统&#xff0c;并在此系统安装docker和docker-compose提供环境支持 3.运行docker-compose指令为docker-compose up -d即可运行当前目录下的文件 Redis未授权访问漏洞 一、进…

流程挖掘,为光伏企业重塑确定的竞争力

2023年12月15日&#xff0c;2023中资光伏出海大会在江苏南京隆重举行。国内领先的流程挖掘服务商望繁信科技应邀出席大会&#xff0c;与来自海内外的重要领导、重磅嘉宾、行业大咖齐聚一堂&#xff0c;聚焦“数字化、全球化、本地化”&#xff0c;共同探讨中资光伏企业的出海机…

windows11 DNS手动配置过DNS,在恢复成自动获取后,无法自动获取到DNS,网卡里面DNS还是显示之前手动配置的DNS

windows11 DNS一开始手动配置过DNS,然后在恢复成自动获取后&#xff0c;网卡无法自动获取到DNS&#xff0c;并且网卡里面DNS显示还是之前手动配置的DNS。 系统版本&#xff1a;windows11 企业版、版&#xff1a;10.0.22621 版本 22621 解决办法&#xff1a; 注册表“HKEY_LO…

RHEL9网络设定及网络脚本

1. 添加一张网卡 2. 重写一个网卡配置文件 [rootlocalhost ~]# cd /etc/NetworkManager/system-connections/ [rootlocalhost system-connections]# ls ens160.nmconnection [rootlocalhost system-connections]# vim ens224.connection [rootlocalhost system-connections…

大模型应用(六)如何写好一个prompt,理论+实践

前言 设定是四个基本要素之一&#xff0c;没有一个好的prompt&#xff0c;就绝对不可能有一个好的agent。 如何写prompt的大部分是我粘的&#xff0c;实在是懒得写了。不感兴趣可以直接跳实战。 什么是prompt 大语言模型&#xff08;LLM&#xff09;的能力并不是被设计出来…

【数据结构与算法】迷宫求解------回溯法

回溯法 一.迷宫求解算法二.二维数组表示地图1.地图2.初始化地图3.地图的打印 三.进入迷宫四.栈的实现五.迷宫内探1.首先判断我们的入口2.入栈做标记3.开始探险4.出口判断5.能否下一步6.做标记7.不能下一步 六.运行结果 一.迷宫求解算法 当我们想要找到迷宫的出口,那我们在计算…

为什么网站要使用HTTPS访问

网站使用HTTPS访问的原因有很多&#xff0c;主要可以归纳为以下几个关键点&#xff1a; 1、数据安全性&#xff1a;HTTPS使用SSL/TLS协议对通信过程进行加密&#xff0c;确保信息在传输过程中不被窃取、篡改或冒充。对于涉及敏感信息&#xff08;如个人身份、信用卡号等&#x…

AnyGPT: Unified Multimodal LLM with Discrete Sequence Modeling

发表时间&#xff1a;arXiv 2024年2月26日 论文链接&#xff1a;https://arxiv.org/pdf/2402.12226 作者单位&#xff1a; Fudan University Motivation&#xff1a; LLM 在理解和生成人类语言方面表现出非凡的能力。但是&#xff0c;LLM 的能力仅限于针对文本的处理。而现实…

JVM系列 | 对象的消亡2——HotSpot的设计细节

HotSpot 的细节实现 文章目录 HotSpot 的细节实现OopMap 与 根节点枚举根节点类型及说明HotSpot中的实现 OopMap 与 安全点安全点介绍如何保证程序在安全点上&#xff1f; 安全区域记忆集与卡表记忆集卡表 写屏障并发的可达性分析&#xff08;与用户线程&#xff09;并发可达性…

Spring boot框架指南

1. Spring Boot 概述 1.1 定义与起源 Spring Boot是一种基于Spring框架的开源框架&#xff0c;旨在简化Spring应用程序的创建和开发过程。它通过提供一系列默认配置和自动配置功能&#xff0c;减少了开发者在配置上的工作量&#xff0c;使得快速搭建生产级别的Spring应用程序…

OV SSL证书优势及获取渠道

OV证书&#xff0c;即组织验证型SSL证书&#xff0c;通过严格的组织审查流程&#xff0c;为网站提供数据传输加密、身份验证和信息完整性保护。 OV证书优势 1 高信任度 OV证书通过证书颁发机构&#xff08;CA&#xff09;对企业实名认证&#xff0c;包括企业名称、注册地址、…

万能门店小程序开发平台功能源码系统 带完整的安装代码包以及安装搭建教程

互联网技术的迅猛发展和用户对于便捷性需求的不断提高&#xff0c;小程序以其轻量、快捷、无需安装的特点&#xff0c;成为了众多商家和开发者关注的焦点。为满足广大商家对于门店线上化、智能化管理的需求&#xff0c;小编给大家分享一款“万能门店小程序开发平台功能源码系统…