普里姆(prim)和克鲁斯卡尔(Kruskal)

普里姆(prim)算法练习

洛谷 P1194 买礼物

题目描述

又到了一年一度的明明生日了,明明想要买 B 样东西,巧的是,这 B 样东西价格都是 A 元。

但是,商店老板说最近有促销活动,也就是:

如果你买了第 I 样东西,再买第 J 样,那么就可以只花 KI,J​ 元,更巧的是,KI,J​ 竟然等于 KJ,I​。

现在明明想知道,他最少要花多少钱。

输入格式

第一行两个整数,A,B。

接下来 B 行,每行 B 个数,第 I 行第 J 个为 KI,J​。

我们保证 KI,J​=KJ,I​ 并且 KI,I​=0。

特别的,如果 KI,J​=0,那么表示这两样东西之间不会导致优惠。

注意 KI,J​ 可能大于 A。

输出格式

一个整数,为最小要花的钱数。

输入输出样例

输入 #1

1 1
0

输出 #1

1

输入 #2

3 3
0 2 4
2 0 2
4 2 0

输出 #2

7

说明/提示

样例解释 2.

先买第 2 样东西,花费 3 元,接下来因为优惠,买 1,3 样都只要 2 元,共 7 元。

(同时满足多个“优惠”的时候,聪明的明明当然不会选择用 4 元买剩下那件,而选择用 2 元。)

数据规模

对于 30% 的数据,1≤�≤101≤B≤10。

对于 100% 的数据,1≤B≤500,0≤A,KI,J​≤1000。

2018.7.25新添数据一组

解题思路

采用邻接矩阵储存图,对于注意 KI,J​ 可能大于 A。我们在邻接矩阵的输入时可以取m毕竟打折更贵那我还不如不要打折,如果两者没有联系也可以取m代表两者联系的打折价格后就是m,这样处理就只用构造一棵最小生成树了

AC代码

#include
int a[501][501], dis[501], book[501];//book标记这个物品是否买了
int main()
{
	int m, n, i, j;
	scanf("%d %d", &m, &n);
	for (i = 1; i <= n; i++)
		for (j = 1; j <= n; j++)
		{
			scanf("%d", &a[i][j]);
			if ((a[i][j] > m) || (i != j && a[i][j] == 0))//如果打折后更贵或两个物品之间没有折扣
				a[i][j] = m;
			
		}
	for (i = 1; i <= n; i++)//取1点为起始点
		dis[i] = a[1][i];
	book[1] = 1;
	int sum = m;//初始购买物品1。
	for (i = 2; i <= n; i++)
	{
		int min = 1e9, k = 1;
		for (j = 2; j <= n; j++)
			if (book[j] == 0 && min > dis[j])//先找到价格最便宜的
			{
				k = j; min = dis[j];
			}
		if (k == 1)//k==1代表所有物品买了
			break;
		sum += min; book[k] = 1;//统计和并标记
		for (j = 2; j <= n; j++)//松弛
			if (book[j] == 0 && dis[j] > a[k][j])
				dis[j] = a[k][j];
	}
	printf("%d", sum);
	return 0;
}

克鲁斯卡尔(Kruskal)算法练习

洛谷 P1396 营救

题目背景

“咚咚咚……”“查水表!”原来是查水表来了,现在哪里找这么热心上门的查表员啊!小明感动得热泪盈眶,开起了门……

题目描述

妈妈下班回家,街坊邻居说小明被一群陌生人强行押上了警车!妈妈丰富的经验告诉她小明被带到了 t 区,而自己在 s 区。

该市有 m 条大道连接 n 个区,一条大道将两个区相连接,每个大道有一个拥挤度。小明的妈妈虽然很着急,但是不愿意拥挤的人潮冲乱了她优雅的步伐。所以请你帮她规划一条从 s 至 t 的路线,使得经过道路的拥挤度最大值最小。

输入格式

第一行有四个用空格隔开的 n,m,s,t,其含义见【题目描述】。

接下来 m 行,每行三个整数 u,v,w,表示有一条大道连接区 u 和区 v,且拥挤度为 w。

两个区之间可能存在多条大道

输出格式

输出一行一个整数,代表最大的拥挤度。

输入输出样例

输入 #1

3 3 1 3
1 2 2
2 3 1
1 3 3

输出 #1

2

说明/提示

数据规模与约定

  • 对于 30% 的数据,保证 n≤10。
  • 对于 60%的数据,保证 n≤100。
  • 对于 100% 的数据,保证 1≤n≤10^4,1≤m≤2×10^4,w≤10^4,1≤s,t≤n。且从 s 出发一定能到达 t 区。

样例输入输出 1 解释

小明的妈妈要从 1 号点去 3 号点,最优路线为 1->2->3。

解题思路

本题采用克鲁斯卡尔(Kruskal)算法时间消耗相对较少,在建造最小生成树的过程中并不需要建好在找s到t的最小生成树,只需要在建造过程中建造完一条边后s能够到t,那么刚刚建造的那条边就是最小拥挤度,因为刚开始的时候根据拥挤度进行排序了的。

#include
struct node {//l点到r点的拥挤度为data
	int l, r, data;
}a[20001];
int n, m, s, t;
void kp(int x, int y)//快速排序不多说
{
	if (x >= y)return;
	int left = x, right = y;
	struct node temp = a[x], t;
	while (left < right)
	{
		while (a[right].data >= temp.data && left < right)
			right--;
		while (a[left].data <= temp.data && left < right)
			left++;
		if (left < right)
		{
			t = a[left]; a[left] = a[right]; a[right] = t;
		}
	}
	a[x] = a[left]; a[left] = temp;
	kp(x, left - 1);
	kp(left + 1, y);
}
int b[10001];//并查集数组
int find(int x)
{
	if (b[x] == 0)
		return x;
	else
	{
		b[x] = find(b[x]);//路径压缩
		return b[x];
	}
}
int main()
{
	scanf("%d %d %d %d", &n, &m, &s, &t);
	for (int i = 1; i <= m; i++)
		scanf("%d %d %d", &a[i].l, &a[i].r, &a[i].data);
	kp(1, m);//升序排序
	for (int i = 1; i <= m; i++)
	{
		int X = find(a[i].l), Y = find(a[i].r);
		if (X != Y)
			b[X] = Y;
		if (find(s) == find(t))//有s与t有联系
		{
			printf("%d", a[i].data);//输出这个拥挤度
			return 0;//直接结束
		}
	}
	return 0;
}

 洛谷 P1195 口袋的天空

题目背景

小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。

有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。

题目描述

给你云朵的个数 N,再给你 M 个关系,表示哪些云朵可以连在一起。

现在小杉要把所有云朵连成 K 个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。

输入格式

第一行有三个数 N,M,K。

接下来 M 行每行三个数 X,Y,L,表示 X 云和 Y 云可以通过 L 的代价连在一起。

输出格式

对每组数据输出一行,仅有一个整数,表示最小的代价。

如果怎么连都连不出 K 个棉花糖,请输出 No Answer

输入输出样例

输入 #1

3 1 2
1 2 1

输出 #1

1

说明/提示

对于 30%的数据,1≤N≤100,1≤M≤10^3;

对于 100% 的数据,1≤N≤10^3,1≤M≤10^4,1≤K≤10,1≤X,Y≤N,0≤L<10^4。

 解题思路

坑点:建立结点数量n的最小生成树需要n-1,n个结点中建立k个最小生成树需要n-k条边,n个结点的图中两种情况无法建成k个最小生成树 1.m

其余克鲁斯卡尔算法操作,一个变量统计最小代价。

AC代码

#include
struct node {//l点到r点的拥挤度为data
	int l, r, data;
}a[10001];
int n, m, s, t;
void kp(int x, int y)//快速排序
{
	if (x >= y)return;
	int left = x, right = y;
	struct node temp = a[x], t;
	while (left < right)
	{
		while (a[right].data >= temp.data && left < right)
			right--;
		while (a[left].data <= temp.data && left < right)
			left++;
		if (left < right)
		{
			t = a[left]; a[left] = a[right]; a[right] = t;
		}
	}
	a[x] = a[left]; a[left] = temp;
	kp(x, left - 1);
	kp(left + 1, y);
}
int b[1001];//并查集数组
int find(int x)
{
	if (b[x] == 0)
		return x;
	else
	{
		b[x] = find(b[x]);//路径压缩
		return b[x];
	}
}
int main()
{
	int n, m, k, sum = 0, i, count = 0;//sum统计代价,count统计建造的边的数量
	scanf("%d %d %d", &n, &m, &k);
	for (i = 1; i <= m; i++)
		scanf("%d %d %d", &a[i].l, &a[i].r, &a[i].data);
	if (m < n - k || n < k)//无法构成最小生成树
	{
		printf("No Answer\n");
		return 0;//直接结束
	}
	kp(1, m);//升序排序
	for (i = 1; i <= m; i++)
	{
		if (count == n - k)//已经建立了n-k条边直接结束
			break;
		int x = find(a[i].l), y = find(a[i].r);
		if (x != y)//两点第一次联系,建造边
		{
			count++;//建造边的数量+1
			sum += a[i].data;//统计和
			b[x] = y;
		}
		
	}
	printf("%d", sum);
	return 0;
}

你可能感兴趣的:(题组,学习,图论)