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