交作业(解法一)

版权声明:未经蒟蒻博主同意,不得转载 https://blog.csdn.net/qq_41735385/article/details/86917214

Problem : 交作业

Time Limit: 2 Sec Memory Limit: 128 MB

Description

又到了交作业的时间了,都要找wyf学霸抄作业,但wyf只会借作业给朋友,幸好朋友会借给朋友的朋友.但不是朋友就不会分享,问最后谁写了作业,朋友是相互的,wyf编号是1

Input

第一行n,m,学生数和m对朋友,下来m行,ai和bi是朋友

Output

谁写了作业

Sample Input

10 9
1 4
1 8
1 7
2 3
4 8
5 6
6 7
6 8
9 10

Sample Output

1 4 5 6 7 8

HINT

题意分析:

首先看到这一题,我就先想到了连通块,然后就想到能不能用并查集来解决。
因为这一题的要求就是让我们求包括wyf在内的与wyf是朋友或间接朋友的人有多少。
自然就能联想到并查集

我们来分析一下样例:

画的丑,见谅
好了,我们清晰地能够知道,与wyf(1)为朋友或间接朋友的人[当然包括wyf]总共有6人,即1,4,5,6,7,8,要求我们输出的也就是这6个数,输出应该潜含条件:升序输出。

BUT:

本蒟蒻发现,以我自己的水平,完全不能用并查集来求解[没学好],
所以我又想到了最短路。。。
是的,你没有听错,就是最短路!!!
让我们先回忆一下,解决最短路的4种方法:

1. Floyed-Warshall算法(N3
2. Dijkstra算法(N2
3. Bellman-Ford算法(VE)
4. SPFA算法(kE)[一般地,k<=2]

[后两种算法,本蒟蒻还没掌握好,而且今天的重头戏在前两种算法]

我们来看到1. Floyed-Warshall算法(N3,相信大家都知道Floyed算法的变形有一个很好的用处:判断两点是否连通

具体代码下:
//请无视本蒟蒻注释掉的恶心并查集代码
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;

int n,m,a,b/*,fa[1001]*/;
bool g[1001][1001]; //表示u、v是否连通

/*int find(int x)
{
	return x==fa[x]? fa[x]:fa[x]=find(fa[x]);
}*/

int main()
{
	scanf("%d%d",&n,&m);
//	for (int i=1;i<=n;i++) fa[i]=i;
	g[1][1]=true; //从wyf到自身一定是朋友的
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		/*int x=find(a),y=find(b);
		if (a==1)
		{
			fa[y]=x;
		}
		else if (b==1)
		{
			fa[x]=y;
		}
		else
		{
			if (x==1) fa[y]=x;
			else if (y==1) fa[x]=y;
			else fa[y]=x;
		}*/
		g[a][b]=g[b][a]=true; //默认无向图
	}
	/*for (int i=1;i<=n;i++)
		if (fa[i]==1)
		{
			printf("%d ",i);
		}*/
	for (int d=1;d<=n;d++)
		for (int u=1;u<=n;u++)
			for (int v=1;v<=n;v++)
				if (d!=u&&u!=v&&v!=d)
					g[u][v]=g[u][v]||(g[u][d]&&g[d][v]);
					/*
					 若u to v的直接路径
					 或
					 u to d(中间点)且d(中间点) to v的间接路径
					 其中有一条路径是相通的,则 u、v两点是连通的
					 注意用()更加的清晰
					 */
	//朴素的Floyed算法的变形
	for (int i=1;i<=n;i++)
		if (g[1][i]) printf("%d ",i);
		//若1 to i的路径是true,输出
	return 0;
}

但N3的时间复杂度,让人很伤脑筋,所以这样做TLE到飞起。

我们再来看2. Dijkstra算法,它难道能够用来判断两点是否连通吗?
的确能行!我们同样将其变形,就能得到答案了。

AC代码如下:

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;

int n,m,a,b,u,tot; //tot表示能写完作业的总人数
bool g[1001][1001]; //邻接表表示两人是否为朋友
bool vis[1001],dis[1001],stu[1001];
// vis表示当前节点是否被访问,dis表示1与v是否连通,stu表示当前节点是否已经与1连通

void Dijkstra()
{
	for (int i=1;i<=n;i++) dis[i]=g[1][i]; //先初始化
	vis[1]=g[1][1]=true; //标记1已被访问,wyf to自身是连通的
	for (int i=1;i<n;i++)
	{
		u=0; //u点初始化
		for (int j=1;j<=n;j++)
			if (!vis[j]&&dis[j]) {u=j;break;}
			//一旦找到一个节点未被访问且与1连通,记录下来,并退出循环
		if (!u) break; //如果u未被更新,证明都已被访问,结束
		vis[u]=true; //u点打上标记
		for (int v=1;v<=n;v++)
			if (u!=v) //不能枚举与u重合的点
			{
				dis[v]=dis[v]||(dis[u]&&g[u][v]);
				//与Floyed相似,不再多作解释
				if (dis[v]&&!stu[v]) tot++,stu[v]=true;
				//很关键的控制格式
				//如果当前节点是连通的,且这个点还未被打上标记,总人数增加,且标记为已记录
			}
	}	
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		g[a][b]=g[b][a]=true; //无向图
	}
	Dijkstra(); //Dijkstra变形
	for (int i=1;i<=n;i++) //1 to n,完成升序输出
		if (dis[i]) //如果是连通的
		{
			printf("%d",i);
			if (--tot) putchar(' ');
			/*
			 相当于:
			 tot--;
			 if (tot) putchar();
			 就是说,当输出不为最后一个时,输出空格
			*/
		}
	return 0;
}

然而OJ很坑。。。它完全没有要求格式,于是可以少开一个逻辑数组。
我给大家看一个更加容易理解的博客:交作业,非常简单。

如有不当之处,请各位神犇及时指出!同时也希望有人能告诉我怎样用并查集做,谢谢。

展开阅读全文

没有更多推荐了,返回首页