ACM 预备队训练第九周 图论初步

本周学习内容(以下题目所涉及的):

1.并查集        2.临接表存储图        3.dfs求每个点走过次数        4.链式前向星存储图        5. 黑白染色        6.反向建图        7.Floyd算法;

一.P3367 【模板】并查集

思路:本题为并查集的模版,是本周唯一友好的题目。

        本题使用并查集+压缩节点;f[ ]数组存储各点的祖先,fa( x )函数负责找到一个点(x)的祖先节点。至于压缩路径,fa()与f[]其实他们的压缩路径就是记忆化的斐波那契数列的思路。

code

#include<iostream>
using namespace std;
#define M 1e5
int f[100002],n,m;
int fa(int x)
{
    if (f[x]!=x)                       
        f[x]=fa(f[x]);        //更新祖先节点,压缩路径。 
    return f[x];
}
int main()
{
	cin>>n>>m;
	for (int i=0;i<=n;i++) 
		f[i]=i;
	while (m--)
	{
		int z,x,y;
		cin>>z>>x>>y;
		if (z==1)
		{
			//f[x]=y;
			f[fa(x)]=fa(y);
		}
		else
		{
			if (fa(x)==fa(y)) cout<<"Y"<<endl;
			else cout<<"N"<<endl;
		}
	}
	return 0;
}

二.P8604 [蓝桥杯 2013 国 C] 危险系数

思路:可以使用 dfs(深度优先搜索)求解,求出 u 到 v 间的每一条路径,将路径总数统计,并将被经过的点被经过总数加一。如果一个点被经过的次数与总路径条数相等,那么这一个点就是 u 和 v 的关键点。

code

#include<iostream>
#include<vector>
#define int long long
using namespace std;
vector<int>edges[1010];//临接表存储图
vector<bool>visited(1010);//记录是否走过
vector<int>vexnum(1010);//记录每个点走过次数
int n, m, start, endd, ans = 0, sum = 0;
 
void dfs(int cur) {
	if (cur == endd) {
		sum++;//路径总数
		for (int i = 1; i <= n; i++) {
			if (visited[i]==1) {
				vexnum[i]++;//每个点走过次数
			}
		}
	}
	else {
		int len = edges[cur].size();
		for (int i = 0; i < len; i++) {
			int value = edges[cur][i];
			if (!visited[value]) {
				visited[value] = 1;
				dfs(value);
				visited[value] = 0;
			}
		}
	}
}
signed main()
{
	cin >> n >> m;
	int v1, v2, len;
	for (int i = 0; i < m; i++) {
		cin >> v1 >> v2;
		edges[v1].push_back(v2);
		edges[v2].push_back(v1);
	}
	cin >> start >> endd;
	visited[start] = 1;
	dfs(start);
	if (sum > 0) {
		for (int i = 1; i <= n; i++) {
			if (vexnum[i] == sum) {//是否存在点走过次数与路径总数相等的点
				ans++;//关键点计数
			}
		}
		cout << ans - 2 << endl;//减2是因为统计时多加了起点和终点
	}
	else {
		cout << "-1" << endl;
	}
	return 0;
}

三.P1330 封锁阳光大学

思路:链式前向星+黑白染色

首先,肯定要明确一点,那就是这个图是不一定联通的。于是,我们就可以将整张图切分成许多分开的连同子图来处理。

题意说白了就是:每一条边都有且仅有一个被它所连接的点被选中。又因为我们要处理的是一个连通图。所以,对于这一个图的点的选法,可以考虑到相邻的点染成不同的颜色。于是,对于一个连通图,要不就只有两种选法(因为可以全部选染成一种色的,也可以全部选染成另一种色的),要不就是impossible!

所以,我们只需要找到每一个子连通图,对它进行黑白染色,然后取两种染色中的最小值,然后最后汇总,就可以了。

code

#include<bits/stdc++.h>
using namespace std;
const int M=200001;
struct edge
{
	int to,nexth;
} e[M];
int head[M],cnt=0;
void add(int f,int t)
{
	cnt++;
	e[cnt].to=t;
	e[cnt].nexth=head[f];
	head[f]=cnt;
}
bool vis[M];
int col[M],sum[2];//col每一个点的染色; sum黑白两种染色各自的点数
bool dfs(int node,int color)//染色(返回false即impossible)
{
    if(vis[node])//如果已被染过色
    {
        if(col[node]==color) return true;//如果仍是原来的颜色,即可行
        return false;//非原来的颜色,即产生了冲突,不可行
    }
    vis[node]=true;//记录
    sum[col[node]=color]++;//这一种颜色的个数加1,且此点的颜色也记录下来
    bool tf=true;//是否可行
    for(int i=head[node];i!=0&&tf;i=e[i].nexth)//遍历边
    {
        tf=dfs(e[i].to,1-color);//是否可以继续染色
        /*这道题不用回溯。只有在那些需要求出所有可能路径的题目中才需要回溯操作。
		为什么呢?其实回溯操作类似于一个“遗忘操作”;使得在寻找第二条路径时可以找到这个相同的结点。
		而这道题并不需要求出所以可能路径。只要把所有的结点都走一遍,并不是要求出所有可能路径。*/
    }
    return tf;//返回是否完成染色
}
int main()
{
	int n,m;
    scanf("%d%d",&n,&m);
    int a,b;
    while(m--)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);//存的是有向边,所以存两次
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;//如果此点已被包含为一个已经被遍历过的子图,则不需重复遍历
        sum[0]=sum[1]=0;//初始化
        if(!dfs(i,0))//如果不能染色
        {
            printf("Impossible");
            return 0;//直接跳出
        }
        ans+=min(sum[0],sum[1]);//加上小的一个 可能不在一个连通图 
    }
    printf("%d",ans);//输出答案
    return 0;
}

四.P3916 图的遍历

思路:反向建边 + dfs

按题目来每次考虑每个点可以到达点编号最大的点,不如考虑较大的点可以反向到达哪些点

循环从N到1,则每个点i能访问到的结点的A值都是i

每个点访问一次,这个A值就是最优的,因为之后如果再访问到这个结点那么答案肯定没当前大了

code

#include <bits/stdc++.h>
using namespace std;
const int M = 1e5+2;
int n,m,res[M];
vector <int> q[M];
void dfs(int now1,int goal)
{
	res[now1]=goal;
	for(int k=0;k<q[now1].size();k++)
	{
		int from=q[now1][k];
		if (res[from]==0)
		{
			dfs(from,goal);
		}
	}
}
int main()
{
	cin>>n>>m;
	while (m--)
	{
		int x,y;
		cin>>x>>y;
		q[y].push_back(x); 
	}
	for(int i=n;i>=1;i--){
        if(res[i]==0){
            //res[i]=i;
            dfs(i,i);
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",res[i]);
	return 0;
}

五.P1119 灾后重建

思路:Floyd算法
/*Floyd算法:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。*/
/*本题思路:所有的边全部给出,按照时间顺序更新每一个可用的点(即修建好村庄),对于每个时间点进行两点之间询问,求对于目前建设的所有村庄来说任意两点之间的最短路不正好就是Floyd算法中使用前k个节点更新最短路的思维吗?*/

code

#include<bits/stdc++.h>
using namespace std;
const int N=202;
int f[N][N],n,m,a[N],vis;
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
	scanf("%d",&a[i]);//依次输入每一个村庄建立完成时需要的时间
	for(int i=0;i<n;i++)
	for(int j=0;j<n;j++)
		f[i][j]=0x3f3f3f3f;//初始化为保证它不爆炸范围内的最大值 
	for(int i=0;i<n;i++)
	    f[i][i]=0;
	int s1,s2,s3;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&s1,&s2,&s3);
		f[s1][s2]=s3;
		f[s2][s1]=s3; 
	}
	int x,y,tt,num;
	cin>>num;
	while(num--)
	{
		cin>>x>>y>>tt;
		for (int i=0;i<n;i++)
		{
			if (vis!=0)
			if (a[i]<=vis) continue;
			if (a[i]>tt) continue;
			for (int j=0;j<n;j++)
			for (int k=0;k<n;k++) 
			{
			f[j][k]=min(f[j][k],f[j][i]+f[k][i]);
			f[k][j]=f[j][k];
			//cout<<f[x][y]<<endl;
			}
		}
		if(a[x]>tt||a[y]>tt)cout<<-1<<endl;
		else 
		{
			if(f[x][y]==0x3f3f3f3f)cout<<-1<<endl;
			else cout<<f[x][y]<<endl;	
		}
		vis=max(vis,tt);  
	}
    return 0;
}

世界地图矢量数据可以通过多种网站进行下载。以下是一些提供免费下载世界地图矢量数据的网站: 1. Open Street Map (https://www.openstreetmap.org/): 这个网站可以根据输入的经纬度或手动选定范围来导出目标区域的矢量图。导出的数据格式为osm格式,但只支持矩形范围的地图下载。 2. Geofabrik (http://download.geofabrik.de/): Geofabrik提供按洲际和国家快速下载全国范围的地图数据数据格式支持shape文件格式,包含多个独立图层,如道路、建筑、水域、交通、土地利用分类、自然景观等。数据每天更新一次。 3. bbbike (https://download.bbbike.org/osm/): bbbike提供全球主要的200多个城市的地图数据下载,也可以按照bbox进行下载。该网站还提供全球数据数据格式种类齐全,包括geojson、shp等。 4. GADM (https://gadm.org/index.html): GADM提供按国家或全球下载地图数据的服务。该网站提供多种格式的数据下载。 5. L7 AntV (https://l7.antv.antgroup.com/custom/tools/worldmap): L7 AntV是一个提供标准世界地图矢量数据免费下载的网站。支持多种数据格式下载,包括GeoJSON、KML、JSON、TopJSON、CSV和高清SVG格式等。可以下载中国省、市、县的矢量边界和世界各个国家的矢量边界数据。 以上这些网站都提供了世界地图矢量数据免费下载服务,你可以根据自己的需求选择合适的网站进行下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值