讨论
图论,广搜,确切说是补图的广搜,对于给定的图G,其补图H中,原来G中存在的边在H中都不存在,原来G中没有的边在H中都存在,把G和H拼在一起就是一个完全图,算法很简单,对于一个点,找出与其不直连的点,向这些点广搜,重复这个过程即可
实现层面上需要一点技巧,也只是一点,当已经处理过所有N个点之后就可以弹出了,这样虽然复杂度没有改进(每个点都要扫一遍与其直连的边),但是对于边数较少的图可以非常快的完成,比如200000个点,0个边,由于第一次就完成了所有点的处理,直接弹出就可以了,否则需要对所有点都进行一次广搜,会非常慢的
比赛的时候竟然看了半天看不懂题意,未免有点太神经过敏了,而后来打扫的时候由于邻接表开小了一半导致一排TLE(只有尝试写入越界才会RE),如果是java的话恐怕第一次RE马上就能反应到
为了能稍微快一点(实际上快了100+ms),对memset和输出进行了一点优化
题解状态
546MS,4576K,1496 B,C++
题解代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 200004
#define MAXM 40004
#define memset0(a) memset(a,0,sizeof(a))
int N, M, S;//点数 原图的边数 起点
int to[MAXM], pre[MAXM], al[MAXN], index, dis[MAXN];//前四个是邻接表的 分别是边指向的点 上一条边的下标 每个起点最后插入的边的下标 分配下表的变量 dis则是最短距离
bool used[MAXN], mk[MAXN];//点已经使用过 存在直连的边
void fun()
{
index = 1;
for (int p = 0; p < M; p++) {
int a, b;
scanf("%d%d", &a, &b);//input
to[index] = b, pre[index] = al[a], al[a] = index++;
to[index] = a, pre[index] = al[b], al[b] = index++;
}
scanf("%d", &S);//input
queue<int>q;
q.push(S);
dis[S] = 0;
used[S] = 1;
int cnt = 1;//已经处理了1个点了
while (!q.empty() && cnt < N) {
memset(mk, 0, (N + 1)*sizeof(bool));
int a = q.front();
q.pop();
for (int p = al[a]; p; p = pre[p])
mk[to[p]] = 1;//先得出与哪些点是直连的
for (int p = 1; p <= N; p++)
if (!used[p] && !mk[p]) {//再处理非直连且没用过的点
q.push(p);
used[p] = 1;
cnt++;
dis[p] = dis[a] + 1;
}
}
if (S == 1) {//下面这些只是对输出进行了一点处理 没什么特别意义
printf("%d", dis[2]);//output
for (int p = 3; p <= N; p++)
printf(" %d", dis[p]);//output
}
else {
printf("%d", dis[1]);//output
for (int p = 2; p < S; p++)
printf(" %d", dis[p]);//output
for (int p = S + 1; p <= N; p++)
printf(" %d", dis[p]);//output
}
printf("\n");//output
}
int main(void)
{
freopen("vs_cin.txt", "r", stdin);
freopen("vs_cout.txt", "w", stdout);
int times;
while (~scanf("%d", ×)) {
while (times--) {
scanf("%d%d", &N, &M);
fun();
memset(al, 0, (N + 1)*sizeof(int));
memset(used, 0, (N + 1)*sizeof(bool));
}
}
}
EOF