bzoj2140对tarjan算法的一些理解

最近,又理解了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;
	}
	else
		return 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");
		else
			printf("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);
	}

*/

一份好写的代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h>
using namespace std;
struct edgee
{
	int from, to;
	edgee(int f, int t) :from(f), to(t)
	{}
	edgee(){}
};
edgee edge[30000];
int first[10000], nextt[30000],edgetot;
int root;
int allp;
pair<int, int>p[5000];
struct triee
{
	int son[52];
	int value, num;
};
triee trie[100000];
int diantot = 1,timee=0;
int all = 1,n,m;
int c(char a)
{
	if (a >= 'A'&&a <= 'Z')
	{
		return a - 'A' + 26;
	}
	else
		return a - 'a';
}
int insert(char *a)
{
	int temp = root; int len = strlen(a);
	for (int i = 0; i < len; i++)
	{
		int kind = c(a[i]);
		if (trie[temp].son[kind] == 0)trie[temp].son[kind] = diantot++;
		temp = trie[temp].son[kind];
	}
	if(trie[temp].num==0)trie[temp].num = all++;
	return trie[temp].num;
}
void addedge(int from, int to)
{
	edge[edgetot] = edgee(from, to);
	nextt[edgetot] = first[from];
	first[from] = edgetot++;

}
int dfn[10000], low[10000], state[10000], belong[10000], isinstack[10000], stack[10000];
int top = -1,circlenum=1;
void dfs(int num, int fa)
{
	dfn[num] = low[num] = timee++; state[num] = 1; isinstack[num] = 1; stack[++top] = num;
	for (int i = first[num]; i != -1; i = nextt[i])
	{
		int to = edge[i].to;
		if (!state[to])
		dfs(to, num);
		if (dfn[num] < dfn[to])
			low[num] = min(low[num], low[to]);
		else
			if (isinstack[to])
			low[num] = min(dfn[to], low[num]);
	}
	if (dfn[num] == low[num])
	{
		while (1)
		{
			belong[stack[top]] = circlenum;
			if (stack[top] == num)
			{
				top--; isinstack[num] = 0; break;
			}
			isinstack[stack[top]] = 0;
			top--;
		}
		circlenum++;
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= 2 * n; i++)first[i] = -1;
	for (int i = 0; i < n; i++)
	{
		char a[10], b[10];
		scanf("%s%s", a, b);
		int aa=insert(a); int bb=insert(b);
		p[allp].first = aa; p[allp].second = bb; allp++;
		addedge(bb, aa);
	}
	scanf("%d", &m);
	for (int i = 0; i < m; i++)
	{
		char a[10], b[10];
		scanf("%s%s", a, b);
		int aa = insert(a); int bb = insert(b);
		addedge(aa, bb);
	}
	for (int i = 1; i <= all; i++)
	{
		if (state[i])continue;
		dfs(i, 0);
	}
	for (int i = 0; i < allp; i++)
	{
		int a = p[i].first; int b = p[i].second;
		if (belong[a] == belong[b])
			printf("Unsafe\n");
		else
			printf("Safe\n");
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值