bzoj2140对tarjan算法的一些理解

来源:互联网 发布:如何优化页面响应速度 编辑:程序博客网 时间:2024/04/29 04:04

最近,又理解了tarjan有了新的感悟,以前的tarjan删掉了,因为写的都是垃圾。。现在我感觉因该阔以了。。。。(题目分析在后面)

基础就不说了,网上一大堆。。说重点。。。

我对tarjan的大体理解就是。首先dfs一颗树在这颗树中我们只管前向边和后向边,横向边是要忽略的。嗯,所以看tarjan之前一定要把算法导论关于dfs的一些性质看好了再来理解就好了。。

对于tarjan最大的问题就在于   

if (dfn[to] > dfn[temp])
     {
      low[temp] = min(low[temp], low[to]);
     }
     else
     {
      if (isinstack[to] == true)//这里是为了避免横向边
       low[temp] = min(low[temp], dfn[to]);
     }

这段代码为什么   

 if (isinstack[to] == true)//这里是为了避免横向边
       low[temp] = min(low[temp], dfn[to]);

不能改成

if (isinstack[to] == true)//这里是为了避免横向边
       low[temp] = min(low[temp], low[to]);

呢?对于求强联通分量这样写是对的但如果你要求割点或者是割边这样就是错的。为什么呢?

请看这组数据:

4  4代表有4个点,4条边

点1到点2//注意这些边都是无向边

点2到点3

点3到点4

点4到点2

很明显点2 就是一个割点。but当你改变正确写法时点2就不是了。。自己模拟一边把,从点1开始(虽然算法是从任意点开始都是可行的但是这最好从1开始,不然对于这个图不好找错)

嗯,所以对于正确写法你从任意点开始都是对的,但是对于错误写法,你从某些 点开始就是错的。。如果还不理解就看下面博客

http://www.cnblogs.com/c1299401227/p/5402747.html 看评论才是重点!!!!!

嗯,现在应该全都理解了。。。。由于递归很好写而且会容易爆栈就没写递归,写了个非递归。。。

下面说说此题分析。先开始女向男连边,输入m后是男向女连边。夫妻如果在同一个联通分量里就是unsafe。怎么想出来的先考虑只有2对夫妻。3对夫妻的情况运用km匹配的

思想思考一下就不难出解了。。还有用trie 存名字表hash拉拉。。注意名字是大小写乱入所以还写了个change函数.

#include<iostream>#include<cstdio>#include<algorithm>#include<string.h>#include<stack>using namespace std;struct triee{int son[53];int endnum;};struct edgee{int to;};triee trie[40000];edgee edge[24050];int first[8050], nextt[24050];int root, diantot=1,enddiantotnum=1,edgetot = 1;//到底是从1开始还是0想清楚int low[8055], dfn[8055],state[8055],belong[8055],stackk[8055],match[8055];bool isinstack[8055];stack<int>que;int dfsnum,circlenum,stacknum=-1;int change(char s){if (s >= 'A'&&s <= 'Z'){int q = s - 'A' + 'a' + 26;;return q;}elsereturn s;}int getnum(char *s){int len = strlen(s);int temp = root;for (int j = 0; j < len; j++){if (trie[temp].son[change(s[j]) - 'a'] == 0){int qqq = change(s[j]) - 'a';trie[temp].son[change(s[j]) - 'a'] = diantot++;}temp = trie[temp].son[change(s[j]) - 'a'];}if (!trie[temp].endnum)trie[temp].endnum = enddiantotnum++;return trie[temp].endnum;}void addedge(int a, int b){edge[edgetot].to = b;nextt[edgetot] = first[a];first[a] = edgetot;edgetot++;}void tarjan(int num){if (belong[num] != -1)return;bool ahead = false;dfn[num] = low[num] = dfsnum++; state[num] = 1;isinstack[num] = true; que.push(num); stackk[++stacknum] = num;while (!que.empty()){ahead = false;int temp = que.top();for (int i = first[temp]; i; i = nextt[i]){int to = edge[i].to;if (state[to] == 0){dfn[to] = low[to] = dfsnum++; state[to] = 1;isinstack[to] = true; que.push(to); stackk[++stacknum] = to;ahead = true;break;}}if (!ahead){if (temp == que.top()){for (int i = first[temp]; i; i = nextt[i]){int to = edge[i].to;if (dfn[to] > dfn[temp]){low[temp] = min(low[temp], low[to]);}else{if (isinstack[to] == true)//这里是为了避免横向边low[temp] = min(low[temp], dfn[to]);}}if (dfn[temp] == low[temp]){while (stackk[stacknum] != temp){int to = stackk[stacknum--];belong[to] = circlenum;isinstack[to] = false;}int to = stackk[stacknum--];belong[to] = circlenum;isinstack[to] = false;circlenum++;}}state[temp] = 2;que.pop();}}}int main(){int n;scanf("%d", &n);for (int i = 0; i < n; i++){char a[10], b[10];scanf("%s%s", a, b);int aa, bb;aa = getnum(a); bb = getnum(b);match[aa] = bb; match[bb] = aa;//根据getnum函数里的totnum可知女为奇数,男为偶数;addedge(aa, bb);}int m;scanf("%d", &m);for (int i = 0; i < m; i++){char a[10], b[10];scanf("%s%s", a, b);int aa, bb;aa = getnum(a); bb = getnum(b);addedge(bb, aa);}for (int i = 1; i < enddiantotnum; i++)belong[i] = -1;for (int i = 1; i < enddiantotnum; i++){tarjan(i);}for (int i = 1; i < enddiantotnum; i += 2){if (belong[i] == belong[match[i]])printf("Unsafe\n");elseprintf("Safe\n");}return 0;}/*这段代码与此题无关只是为了补充关于求割点和割边for(int i=2;i<=n;++i)//n当然就是点数啦。。。1是起点也就是根其他点位置在哪无所谓。。。。{int v=father[i];//这里的father[i]就是dfs的时候i的父亲(dfs出的是一颗树啦,虽然我们要看后向边,但是可以把它看成是一颗树吧。if(v==1)rootson++;//统计根节点子树的个数,根节点的子树个数>=2,就是割点else{if (low[i] >= dfn[v])//割点的条件is_cut[v] = true;//父亲是割点}}for(int i=1;i<=n;++i){int v=father[i];if(v>0&&low[i]>dfn[v])//桥的条件    printf("%d,%d\n", v, i);}*/


0 0
原创粉丝点击