week6(图、并查集、最小生成树 + CSP201812-4数据中心)

A - 氪金带东

题目

样例输入

5
1 1
2 1
3 1
1 1

样例输出

3
2
3
4
4

 

思路

如果从每个点暴力搜索可能会超时,需要进行n次bfs。

因此采用一个比较巧妙的方法:从任意一点起始开始进行搜索,搜索到最远的一个节点x,然后再从x搜索距离x节点最远的节点y(可以不准确的理解为寻找圆上两点,两点连线为过任意点的直径)。那么每一个节点到其他节点的最远距离就是x节点或y节点到达该点较大的距离。

具体为:

·从1号节点进行bfs找到最远节点x。

·从x进行bfs,找到最远节点y,同时标记到每一个节点的距离。

·从y进行bfs,标记到每一个节点的距离。

·输出第2次bfs与第3次bfs中标记的最大距离。

通过这个方法,成功将bfs次数降为3次。

图的存储方式是链式前向星(截图来源于程设课课件

 

代码

#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 10010
using namespace std;

int vis1[MAXN], vis2[MAXN], vis3[MAXN], dis1[MAXN], dis2[MAXN], dis3[MAXN];

struct Edge
{
	int u, v, w, next;
}Edge[2 * MAXN];

int head[MAXN], total;

void init()
{
	total = 1;
	for (int i = 0; i < MAXN; i++)
		head[i] = -1;
}


void addEdge(int uu, int vv, int ww)
{
	Edge[total].u = uu;
	Edge[total].v = vv;
	Edge[total].w = ww;
	Edge[total].next = head[uu];
	head[uu] = total;
	total++;
}

void begin()
{
	for (int i = 0; i < MAXN; i++)
	{
		vis1[i] = vis2[i] = vis3[i] = -1;
		dis1[i] = dis2[i] = dis3[i] = 0;
	}
		
}


int bfs(int x, int vis[], int dis[])
{
	queue<int> q;
	q.push(x);
	vis[x] = 1;
	int  max = 0;
	int maxDis = 0;
	while (!q.empty())
	{
		int a = q.front();
		q.pop();
		int nextt = head[a];
		while (nextt != -1)
		{
			int b = Edge[nextt].v;
			if (vis[b] == -1)//没访问过
			{
				q.push(b);
				vis[b] = 1;
				dis[b] = dis[a] + Edge[nextt].w;
				if (dis[b] > maxDis)
				{
					maxDis = dis[b];
					max = b;
				}
			}
			nextt = Edge[nextt].next;
		}
	}
	return max;
}

int main()
{
	int n;
	while (cin >> n)
	{
		begin();
		init();
		for (int i = 2; i <= n; i++)
		{
			int v, w;
			cin >> v >> w;
			addEdge(i, v, w);
			addEdge(v, i, w);
		}

		int a = bfs(1, vis1, dis3);
		int b = bfs(a, vis2, dis1);
		int c = bfs(b, vis3, dis2);

		for (int i = 1; i <= n; i++)
			cout << max(dis1[i], dis2[i]) << endl;
	}
	
		
	//system("Pause");
}

 

 

 

 

B - 戴好口罩!

题目

 

思路

这道题目比较简单,可以采用并查集解决。

并查集要记录每个集合的元素数目。只需要将每个小团体中的每个人所在的集合合并,最后找到0号同学的代表元素,输出其所在集合的人数即可。

 

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#define MAXN 30010
using namespace std;

int an[MAXN];
int rankk[MAXN];

void init()
{
	for (int i = 0; i < MAXN; i++)
	{
		an[i] = i;
		rankk[i] = 1;
	}
}

int find(int x)
{
	if (an[x] == x) return x;
	else return an[x] = find(an[x]);
}

bool unite(int x, int y)
{
	x = find(x);
	y = find(y);
	//cout << "x:" << x << " y:" << y << endl;
	if (x == y) return false;
	if (rankk[x] > rankk[y])
		swap(x, y);
	an[x] = y;
	rankk[y] += rankk[x];
	rankk[x] = rankk[y];
	return true;
}


int main()
{
	int n, m, num, x, y;
	while (cin >> n >> m)
	{
		memset(rankk, 0, sizeof rankk);
		if (n > 0) init();
		else break;
		for (int i = 0; i < m; i++)
		{
			cin >> num >> x;
			for (int j = 1; j < num; j++)
			{
				cin >> y;
				unite(x, y);
				x = y;
				//cout << "sum:" << rankk[0] << endl;
			}
		}
		int z = find(0);
		cout << rankk[z] << endl;
	}
}



 

 

 

 

C - 掌握魔法の东东 I

题目

样例输入

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

样例输出

9

 

思路

如果这道题目没有“黄河之水天上来”,只需要计算如何连通各个田的话,就是一个简单的最小生成树问题,可以使用kruskal算法解决,但是这道题还要考虑每个点如何“黄河之水天上来”,题目就变得复杂了。

解决方法为:将“黄河之水”视为一个特殊节点0号节点,它到各个田的权重即为那个田“黄河之水天上来”的消耗。

因为至少有一次“黄河之水天上来”,所以可以先将所有“黄河之水”的消耗进行排序,选出消耗最小的进行“黄河之水”,然后再将“黄河之水”与饮水一起排序,进行kruskal算法。

kruskal算法为:

在图中找到一条权重最小的边,如果加入这条边不会构成环路,就选择它,否则抛弃它。

重复该过程,直到形成最小生成树。

如果判断了所有边而无法生成最小生成树,则算法失败,说明原图不连通。

注意

因为必须“黄河之水”,所以在kruskal算法开始之前需要先选择出“黄河之水”的田,再进行kruskal算法,并且需要将该边从kruskal算法选择边的范围中删除。

 

代码

#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 100010
using namespace std;

int vis1[MAXN], vis2[MAXN], vis3[MAXN], dis1[MAXN], dis2[MAXN], dis3[MAXN], an[MAXN], rankk[MAXN];

void init()
{
	for (int i = 0; i < MAXN; i++)
	{
		an[i] = i;
		rankk[i] = 1;
	}
}

int find(int x)
{
	if (an[x] == x) return x;
	else return an[x] = find(an[x]);
}

bool unite(int x, int y)
{
	x = find(x);
	y = find(y);
	//cout << "x:" << x << " y:" << y << endl;
	if (x == y) return false;
	if (rankk[x] > rankk[y])
		swap(x, y);
	an[x] = y;
	rankk[y] += rankk[x];
	rankk[x] = rankk[y];
	return true;
}


struct Edge
{
	int u, v, w, next;
	
}Edge[2 * MAXN], e[MAXN];

bool cmp(struct Edge &a, struct Edge &b)
{
	return a.w < b.w;
}


int head[MAXN];
int total = 1, tot = 0;


void init2()
{
	total = 1;
	tot = 0;
	for (int i = 0; i < MAXN; i++)
		head[i] = -1;
}

void insert(int uu, int vv, int ww)
{
	e[tot].u = uu;
	e[tot].v = vv;
	e[tot].w = ww;
	tot++;

}

void addEdge(int uu, int vv, int ww)
{
	Edge[total].u = uu;
	Edge[total].v = vv;
	Edge[total].w = ww;
	Edge[total].next = head[uu];
	head[uu] = total;
	total++;
}

void begin()
{
	for (int i = 0; i < MAXN; i++)
	{
		vis1[i] = vis2[i] = vis3[i] = -1;
		dis1[i] = dis2[i] = dis3[i] = 0;
	}
}


int kruskal(int n, int begin)
{
	int sum = begin, num = 1;
	for (int i = 1; i <= (n * (n - 1) / 2 + n - 1) && num < n; i++)
	{
		if (unite(e[i].u, e[i].v))
		{
			//cout << "w:" << e[i].w << endl;
			sum += e[i].w;
			num++;
		}
	}
	return sum;
}

int main()
{
	int n;
	cin >> n;
	int w;
	init();
	init2();

	for (int i = 1; i <= n; i++)
	{
		cin >> w;
		addEdge(0, i, w);
		addEdge(i, 0, w);
		insert(0, i, w);
	}
	std::sort(e, e + n, cmp);
	unite(e[0].u, e[0].v);
	int b = e[0].w;

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
		{
			cin >> w;
			if (i < j)
			{
				addEdge(i, j, w);
				addEdge(j, i, w);
				insert(i, j, w);
			}
		}

	std::sort(e + 1, e + n * (n - 1) / 2 + n, cmp);
	//cout << "b:"<<b << endl;
	std::cout << kruskal(n, b) << endl;
	std::system("Pause");
}

 

 

 

 

D - 数据中心

题目

 

思路

反复读完题目之后,我终于明白了这个题目在求一颗生成树,使它的最大边最小。

(因为是需要在树的每一层中求出一个最大边,为层的最大边,再在层的最大边中求一个最大边,即为整颗生成树的最大边)

那么直接使用kruskal算法求最小生成树,然后输出最大边即可(root的条件甚至都不需要)。

题目本身并不复杂,就是描述的方式不易理解。

 

代码

#include<iostream>
#include<queue>
#include<algorithm>
#define MAXN 100010
using namespace std;

int an[MAXN], rankk[MAXN];

void init()
{
	for (int i = 0; i < MAXN; i++)
	{
		an[i] = i;
		rankk[i] = 1;
	}
}

int find(int x)
{
	if (an[x] == x) return x;
	else return an[x] = find(an[x]);
}

bool unite(int x, int y)
{
	x = find(x);
	y = find(y);
	//cout << "x:" << x << " y:" << y << endl;
	if (x == y) return false;
	if (rankk[x] > rankk[y])
		swap(x, y);
	an[x] = y;
	rankk[y] += rankk[x];
	rankk[x] = rankk[y];
	return true;
}


struct Edge
{
	int u, v, w, next;

}Edge[2 * MAXN], e[MAXN];

bool cmp(struct Edge &a, struct Edge &b)
{
	return a.w < b.w;
}


int head[MAXN];
int total = 1, tot = 0;


void init2()
{
	total = 1;
	tot = 0;
	for (int i = 0; i < MAXN; i++)
		head[i] = -1;
}

void insert(int uu, int vv, int ww)
{
	e[tot].u = uu;
	e[tot].v = vv;
	e[tot].w = ww;
	tot++;

}

void addEdge(int uu, int vv, int ww)
{
	Edge[total].u = uu;
	Edge[total].v = vv;
	Edge[total].w = ww;
	Edge[total].next = head[uu];
	head[uu] = total;
	total++;
}


int kruskal(int n, int m)
{
	int max = 0, num = 0;
	for (int i = 0; i <= m && num < n - 1; i++)
	{
		if (unite(e[i].u, e[i].v))
		{
			//cout << "w:" << e[i].w << endl;
			num++;
			if (num == n - 1) max = e[i].w;
		}
	}
	return max;
}

int main()
{
	int n, m, r;
	cin >> n >> m >> r;
	int x, y, w;
	init();
	init2();

	for (int i = 0; i < m; i++)
	{
		cin >> x >> y >> w;
		addEdge(x, y, w);
		addEdge(y, x, w);
		insert(x, y, w);
	}

	std::sort(e, e + m, cmp);

	//cout << "b:"<<b << endl;
	std::cout << kruskal(n, m) << endl;
	std::system("Pause");
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值