您的位置:首页 > 其它

hdoj 5876 Sparse Graph(BFS补图)

2016-09-12 20:07 232 查看
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=5876(由于hdoj诡异的排版这一题没办法复制过来贴在这)

当时大连网赛写这道题的时候,我磨了半天没想懂怎么写,第一,我看不懂题意,后来有人告诉我是补图,就是说,每个点之间都是连通的,然后他给出你一些边,这些边要删除,剩下的图才是可行的。第二,有人告诉我是用dijkstra写,但是我怎么看都不像是dijkstra,迷糊了半天,不过也确实是没写过补图的题目。第三,还是题意问题,就不想说了,还是英语太渣,做题太少。

后来看了看题解,马上明白这题怎么写了,我当时觉得用dfs运算量挺大的,没想到是用bfs,因为边才最多20000条,所以暴力去搜就好了。

按照题解的说法,就是:

1,我们知道有n个点和m条边,我们把s之外的点都放进set里面,另外需要把s放进队列q里面。

2,接下来就是,一个while(set不为空),取出队列第一个点,作为起点,用mark标记和他连通的点,这个暴力最多是20000次,如果第一个点遍历了20000次,那么后面的点都不会有边,所以这个复杂度最终只能是m。

3,把set里面的点都遍历一次,set里面存的是未到达的点,然后对照这mark判定,用iter遍历,如果mark[*iter]为真,那么说明这条边已经被删除,不处理。如果为假,说明本身连通,此时用dist[*iter] = dist[从队列中选出的起始点] + 1,在set里面删除这个点,把这个点加入到队列queue中。

就这样2,3步重复。

while(!Set.empty())
{
now = q.front();
q.pop();
memset(mark, 0, sizeof(mark));
len = edge[now].size();
for(i = 0; i < len; i++)
mark[edge[now][i]] = 1;
for(iter = Set.begin(); iter != Set.end(); )//±éÀúÒ»´Îδµ½´ï¹ýµÄµã
{
if(!mark[*iter])//ÅжÏÁ½¸öµãÊÇ·ñÁ¬Í¨£¬Á¬Í¨ÔòÌø¹ý
{
dist[*iter] = dist[now] + 1;
q.push(*iter);
Set.erase(iter++);
}
else
iter++;
}
}

然后就这样写着写着题解我发现了一个严重的问题,如果把set不为空作为条件,那么如果有一个点和其他点的边全都被删除了,那吃枣药丸。比如下面一组数据

3 2

1 2

1 3

1

我发现这样会GG,但我竟然AC了,904ms- -好吧,数据略水,那就改一改吧,用队列不为空作为while循环条件。

然后就TLE了???待我查查错。

原因找到了,就是单纯的用队列不为空做条件的话,那么每个点都要加进去队列,等他们全都出来耗费时间那又是一个200000的复杂度啊,所以结合第一次竟然AC了的经历,我决定在while循环内部最下面加上 if(set为空)break;  这样就不TLE咯。

代码如下:

#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<iostream>
#include<set>
using namespace std;
const int MAXN = 200005;
const int INF = 0x3fffffff;
vector <int> edge[MAXN];
int dist[MAXN],n;

void bfs(int s)
{
set <int> Set;
set <int>::iterator iter;
int now,len,i,cout = 0;
bool mark[MAXN];
queue <int> q;
q.push(s);
dist[s] = 0;

for(i = 1; i < s; i++)
Set.insert(i);
for(i = s + 1; i <= n; i++)
Set.insert(i);

while(!q.empty())
{
now = q.front();
q.pop();
memset(mark, 0, sizeof(mark));
len = edge[now].size();
for(i = 0; i < len; i++)
mark[edge[now][i]] = 1;
for(iter = Set.begin(); iter != Set.end(); )
{
if(!mark[*iter])
{
dist[*iter] = dist[now] + 1;
q.push(*iter);
Set.erase(iter++);
}
else
iter++;
}
if(Set.empty())
break;
}
}

int main()
{
int T,m,u,v,s,i;
while(scanf("%d",&T)!=EOF)
{
while(T--)
{
for(i = 0; i < MAXN; i++)
edge[i].clear();
for(i = 0; i < MAXN; i++)
dist[i] = INF;
scanf("%d%d", &n, &m);
while(m--)
{
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
scanf("%d", &s);
bfs(s);
for(i = 1; i <= n; i++)
if(i != s)
printf("%d%c", dist[i] == INF ? -1 : dist[i], (i == n) || (i == n - 1 && s == n) ? '\n' : ' ');
}
}
return 0;
}

最后还要补充一下map和set的一点知识,就是如果你用erase删除一个数字的时候,其实map和set封装了红黑树在里面,说白了就是个链表,但是网上好像是他们所用的空间是固定的,所以比一般的平衡树效率要高,因为申请空间没那么费劲。那我们从树的角度出发,就是链表一样,那么如果你删除了链表中一个节点,此时,再让iter++寻找下一个节点,他是找不到的,因为那个节点已经被删除了,原本节点空间里面的东西全都是随机生成的数字了,找下一个节点的时候都不知道飞到天上去了,那么就会报错。
比如这样

for(iter = Set.begin(); iter != Set.end(); iter++)
{
if(!mark[*iter])
{
dist[*iter] = dist[now] + 1;
q.push(*iter);
Set.erase(iter);
}
}这样的写法是错误的,因为先删除节点,后++,此时++指向的就不是下一个节点了,所以我们要在删除前就++,来指向下一个节点。也就是我写的程序那样
for(iter = Set.begin(); iter != Set.end(); )
{
if(!mark[*iter])
{
dist[*iter] = dist[now] + 1;
q.push(*iter);
Set.erase(iter++);
}
else
iter++;
}这样就可以不报错且正确了,完。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: