畅通工程系列(hdu 1232 && 1233 && 1863 && 1874 && 1875 && 1879)

8 篇文章 0 订阅
1 篇文章 0 订阅

1232题目:点击打开链接

这题用到了并查集,之前在学最小生成树kruscal的时候简单了解过并查集。

并查集者,并也,查也,统统归于集合也。即其是关于集合的操作——并,查。所以并查集的关键也就是对集合的合并与对元素的查找,查找某一元素属于哪个集合。

下面是百科里面扒的c++代码,个人觉得不难理解。

//判断两点是否在同一个连通分量中
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
int father[50002],a,b,m,n,p;
int find(int x)
{
    if (father[x]!=x) //满足father[x]==x的x就是根节点
        father[x]=find(father[x]);
    return father[x];
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for (int i=1;i<=n;i++) 
         father[i]=i;//初始化,表示每个节点自成一个连通分量
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        a=find(a),b=find(b);father[a]=b;
    }
    for(int i=1;i<=p;i++)
    {
        scanf("%d%d",&a,&b);
        a=find(a);b=find(b);
        if(a==b)printf("Yes");else printf("No");
    }
    return 0;
}

下面是合并两个连通分量的代码:

void merge(int x,int y)
{
    int fx;
    int  fy;
    fx=find(x);
    fy=find(y);
    if(flag[fx]!=fy)
       flag[fx]=fy;
}

上面写的是最普通的并查集,更多的可以戳---> 点击打开链接 这个写的挺明白的。



这一题实际上就是用到最简单的并查集:

最后计算有几个连通分量,要将所有点都连通,就是把连通分量减少到只剩1个,所以需要增加的数目就是现有连通分量数减一

#include <stdio.h>
#include <string.h>

int father[1010];
int n, m;

void init()
{
	int i;
	for(i = 1; i <= n; i++)
		father[i] = i;
}

int find(int x)
{
	while(x != father[x])
		x = father[x];//找到father[x] == x的点就是根节点 
	return x;
}

void combine(int a, int b)
{
	int ra = find(a);
	int rb = find(b);
	if(ra != rb)
		father[ra] = rb;//将ra变成tb的子树 
}

int main (void)
{
	while(scanf("%d", &n) != EOF)
	{
		if(n == 0)
			break;
		scanf("%d", &m);	
		init();
		int i, a, b, j;
		for(i = 0; i < m; i++)
		{
			scanf("%d %d", &a, &b);
			combine(a, b);
		}

		int count = 0;
		for(i = 1; i <= n; i++)
		{
			//printf("%d ", father[i]);
			if(father[i] == i)
				count ++;
		}
		//printf("\n");
		printf("%d\n", count - 1);		
	}		
	return 0;
}


1233题目:点击打开链接

单纯的最小生成树问题。

#include <stdio.h>
#include <string.h>

int map[110][110];
int n, ans;

void init()
{
	int i, j;
	for(i = 0; i <= n; i++)
	{
		for(j = 0; j <= n; j++)
			map[i][j] = 999999;
	}
}

void prim()
{
	int i, j, dis[110];
	bool vis[110];
	memset(vis, 0, sizeof(vis));
	for(i = 1; i <= n; i++)
		dis[i] = map[1][i];
	vis[1] = 1;
	dis[1] = 0;
	for(i = 1; i < n; i++)
	{
		int min = 999999, k;
		for(j = 1; j <= n; j++)
		{
			if(!vis[j] && min > dis[j])
			{
				min = dis[j];
				k = j;
			}
		}
		vis[k] = 1;
		ans += min;
		for(j = 1; j <= n; j ++)
		{
			if(!vis[j] && dis[j] > map[k][j])
				dis[j] = map[k][j];
		}
	}
}

int main(void)
{

	while(scanf("%d", &n) != EOF)
	{
		if(n == 0)
			break;
		init();
		ans = 0;
		int m = n * (n - 1) / 2;
		int i, a, b, x;
		for(i = 0; i < m; i++)
		{
			scanf("%d %d %d", &a, &b, &x);
			if(map[a][b] > x)
				map[a][b] = map[b][a] = x;
		}
		prim();
		printf("%d\n", ans);
	}
	return 0;
}

1863题目: 点击打开链接

这个跟上面的方法是一样的,不同的就是这题要判断不能形成最小生成树的情况。

#include <stdio.h>
#include <string.h>

int map[110][110];
int n, m, ans;

void init()
{
	int i, j;
	for(i = 0; i <= n; i++)
	{
		for(j = 0; j <= n; j++)
			map[i][j] = 999999;
	}
}

void prim()
{
	int i, j, dis[110];
	bool vis[110];
	memset(vis, 0, sizeof(vis));
	for(i = 1; i <= n; i++)
		dis[i] = map[1][i];
	vis[1] = 1;
	dis[1] = 0;
	for(i = 1; i < n; i++)
	{
		int min = 999999, k;
		for(j = 1; j <= n; j++)
		{
			if(!vis[j] && min > dis[j])
			{
				min = dis[j];
				k = j;
			}
		}
		if(min == 999999)
		{
			ans = -1;
			return ;
		}
		vis[k] = 1;
		ans += min;
		for(j = 1; j <= n; j ++)
		{
			if(!vis[j] && dis[j] > map[k][j])
				dis[j] = map[k][j];
		}
	}
}

int main(void)
{

	while(scanf("%d %d", &m, &n) != EOF)
	{//n是村庄数,m道路数 
		if(m == 0)
			break;
		init();
		ans = 0;
		int i, a, b, x;
		for(i = 0; i < m; i++)
		{
			scanf("%d %d %d", &a, &b, &x);
			if(map[a][b] > x)
				map[a][b] = map[b][a] = x;
		}
		prim();
		if(ans == -1)
			printf("?\n");
		else
			printf("%d\n", ans);
	}
	return 0;
}

1874题目: 点击打开链接

这题是单源最短路径问题,思路跟prim也没有差很多,不同的就是在更新数组的地方。

#include <stdio.h>
#include <string.h>
#define MAX 10000000

int map[210][210];
int dis[210];
int sta, end, n, m;

void init()
{
	int i, j;
	for(i = 0; i < n; i++)
	{
		for(j = 0; j < n; j++)
			map[i][j] = map[j][i] = MAX;
	}
} 
void dijkstra()
{
	bool vis[210];
	memset(vis, 0, sizeof(vis));
	int i, j;
	for(i = 0; i < n; i++)
	{
		dis[i] = map[sta][i];
	}
	dis[sta] = 0;
	vis[sta] = 1;
	for(i = 0; i < n; i++)
	{
		int min = MAX, k;
		for(j = 0; j < n; j++)
		{
			if(dis[j] < min && !vis[j])
			{
				min = dis[j];
				k = j;
			}
		}
		vis[k] = 1;
		for(j = 0; j < n; j++)
		{
			if(!vis[j] && dis[j] > min + map[k][j])//这里与prime不同 
				dis[j] = min + map[k][j];
		}
	}
	
}

int main (void)
{
	while(scanf("%d %d", &n, &m) != EOF)
	{
		int i, a, b, x;
		init();
		//memset(map, MAX, sizeof(map));
		for(i = 0; i < m; i++)
		{
			scanf("%d %d %d", &a, &b, &x);
			if(map[a][b] > x || map[a][b] == -1)
				map[a][b] = map[b][a] = x;
		}
		scanf("%d %d", &sta, &end);
		dijkstra();
		/*
		for(i = 0; i < n; i++)
		{
			printf("%d ", dis[i]);
		}
		printf("\n");
		*/
		if(dis[end] == MAX)
			printf("-1\n");
		else
			printf("%d\n", dis[end]);
	}
	return 0;
}

1875题目: 点击打开链接

还是最小生成树问题,但是这里的路径连通是有条件的,在处理的过程中,因为小于10的和大于1000的路径是不能建桥的,所以要将小于10的和大于1000的路径都看成是不存在的,不然会出错………………

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

double map[110][110];
int n;
double ans;

struct node
{
	int x, y;	
};

node city[110];

void init()
{
	int i, j;
	for(i = 0; i <= n; i++)
	{
		for(j = 0; j <= n; j++)
			map[i][j] = 9999999;
	}
}

void prim()
{
	int i, j;
	double dis[110];
	bool vis[110];
	memset(vis, 0, sizeof(vis));
	for(i = 1; i <= n; i++)
	{
		dis[i] = map[1][i];
		//printf("%lf ", dis[i]);
	}
	//printf("\n");
	vis[1] = 1;
	dis[1] = 0;
	for(i = 1; i < n; i++)
	{
		double min = 9999999;
		int k = -1;
		for(j = 1; j <= n; j++)
		{
			if(!vis[j] && min > dis[j] && dis[j] >= 10 && dis[j] < 1000)
			{
				min = dis[j];
				k = j;
			}
		}
		if(min == 9999999)
		{
			ans = -1;
			return ;
		}
		vis[k] = 1;
		ans += min;
		for(j = 1; j <= n; j ++)
		{//如果dis[j]本来的距离是小于10的,而且也没有将小于10的路径初始化为最大值的话
		//那么dis[j]就更新不了,结果就会错 
			if(!vis[j] && dis[j] > map[k][j])
				dis[j] = map[k][j];
		}
	}
}

void count()
{
	double s;
	int i, j;
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= n; j++)
		{
			if(i == j)
				continue;
			s = pow((double)(city[i].x - city[j].x), 2) + pow((double)(city[i].y - city[j].y), 2);
			s = sqrt(s);
			if(s < 10 || s > 1000)//WA了一次,就错在这里 
									//如果这里不这样写的话,更新数组的时候会出错 
				map[i][j] = map[j][i] = 9999999;
			else
				map[i][j] = map[j][i] = s;
		}
	}
}

int main(void)
{
	int t, i;
	scanf("%d", &t);
	while(t --)
	{
		scanf("%d", &n);
		init();
		ans = 0;
		for(i = 1; i <= n; i++)
		{
			scanf("%d %d", &city[i].x, &city[i].y);
		}
		count();
		prim();
		if(ans == -1)
			printf("oh!\n");
		else
		{
			//printf("%lf\n", ans);
			ans *= 100.00000;
			printf("%.1lf\n", ans);
		}
	}
	return 0;
}

1879题目: 点击打开链接

这里不同的就是道路存在已经修了还是没有修的情况。其实也很简单,已经修了的,把修路的成本置为0,没有修的成本不变,这样在prime累加成本的时候已经修的加0,也就相当于没有花钱……就解决问题了。

#include <stdio.h>
#include <string.h>

int map[110][110];
int n, ans;

void init()
{
	int i, j;
	for(i = 0; i <= n; i++)
	{
		for(j = 0; j <= n; j++)
			map[i][j] = 999999;
	}
}

void prim()
{
	int i, j, dis[110];
	bool vis[110];
	memset(vis, 0, sizeof(vis));
	for(i = 1; i <= n; i++)
		dis[i] = map[1][i];
	vis[1] = 1;
	dis[1] = 0;
	for(i = 1; i < n; i++)
	{
		int min = 999999, k;
		for(j = 1; j <= n; j++)
		{
			if(!vis[j] && min > dis[j])
			{
				min = dis[j];
				k = j;
			}
		}
		vis[k] = 1;
		ans += min;
		for(j = 1; j <= n; j ++)
		{
			if(!vis[j] && dis[j] > map[k][j])
				dis[j] = map[k][j];
		}
	}
}

int main(void)
{
	while(scanf("%d", &n) != EOF)
	{
		if(n == 0)
			break;
		init();
		ans = 0;
		int m = n * (n - 1) / 2;
		int i, a, b, x, f;
		for(i = 0; i < m; i++)
		{
			scanf("%d %d %d %d", &a, &b, &x, &f);
			if(f == 1)//这里!! 
				map[a][b] = map[b][a] = 0;
			else if(map[a][b] > x)
				map[a][b] = map[b][a] = x;
		}
		prim();
		printf("%d\n", ans);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值