这个题使我更深理解了TARJAN算法,题意:无向图,每添加一条边后文桥的数量,三种解法:(按时间顺序),1,暴力,每每求桥,听说这样能过,我没过,用的hash判重,这次有俩个参数(n->10w,开不了二维的),怎么判?联系2个参数,我想到了用一个函数,像散列一样,定义关系,我随便写了一个hash[x+y+x/y+y/x+x%y+y%x+x|y],一直WA,虽然未过,但是想到了这个,以后2个参数判重可以用之!2.网上学习了算法,将之缩点成树,每个双连通分量用一个点表示,用一个数组tree[i],点i属于tree[i]值,然后记录下桥,重新用一个FATHER【i】来建树,具体见代码。3.学习了算法后,其实不用缩点!直接搞起!因为tarjan算法本生成树!用后面一个点来标记边即可啊!(开始总部知道怎么标记!)并更加理解了树枝边和返祖边,桥是树枝边,无向图的所有边分为树枝边和返祖边(后向边),都是实际存在的。直接在原图上找LCA(按DFN值来判断)即可。
//(原创于2014.2.18)今复习之用,重新理解后,重新编辑方法3。
在hduj交爆栈,前加一句话:即可AC
#pragma comment(linker, "/STACK:10240000000000,10240000000000")// 申请空间
#include<iostream> //暴力,WA #include<vector> #include<cstring> #include<cstdio> using namespace std; vector<vector<int> >edge(100001); int dfn[100001]; int low[100001]; int visited[100001]; //标记访问 int times=0; //时间戳 int hash[210001]; int num_bridge=0; int min(int a,int b) { if(a<=b)return a; return b; } void tarjan(int u,int fa) //dfs { dfn[u]=low[u]=++times; int daxiao=edge[u].size(); for(int i=0;i<daxiao;i++) { int child=edge[u][i]; if(visited[child]==0) { visited[child]=1; tarjan(child,u); low[u]=min(low[u],low[child]); if(dfn[u]<low[child]&&hash[u+child+2*u%child+2*child%u+3*u/child+3*child/u]<=1) //是桥 { num_bridge++; } } else if(edge[u][i]!=fa) { low[u]=min(dfn[edge[u][i]],low[u]); } } } int main() { int n,m; int tcase=1; while (~scanf("%d%d",&n,&m)&&(n||m)) { for(int i=0;i<=n;i++) { edge[i].clear(); } for(int i=0;i<210001;i++) { hash[i]=0; } int a,b; for(int i=0;i<m;i++) { scanf("%d%d",&a,&b); edge[a].push_back(b); edge[b].push_back(a); hash[a+b+2*a%b+2*b%a+3*a/b+3*b/a]++; } int que;scanf("%d",&que); printf("Case %d: ",tcase); tcase++; while(que--) { times=0; num_bridge=0; for(int i=0;i<=n;i++) { low[i]=dfn[i]=visited[i]=0; } scanf("%d%d",&a,&b); edge[a].push_back(b); edge[b].push_back(a); hash[a+b+2*a%b+2*b%a+3*a/b+3*b/a]++; visited[1]=1; tarjan(1,-1); printf("%d ",num_bridge); } printf(" "); } return 0; }
#include<iostream> //方法2,poj 2000MS AC,HOJ RE(stack over) #include<vector> #include<cstring> #include<cstdio> #include<stack> #include<queue> using namespace std; int dfn[100001]; // int low[100001]; int visited[100001]; //tarjan标记访问 int father[100001]; //缩点后建成一棵树 int head[100001]; bool instack[100001]; int tree[100001]; //每个边双连通分量中的点属于一个集合,函数值1-block int level[100001]; bool mark[100001]; //dfs标记 bool is_bridge[100001]; //标记桥,形成树后,用点来标记边也可, stack<int>s; vector<vector<int> >bridge(100001); int min(int a,int b) { if(a<=b)return a; return b; } struct edges //边 { int pre,to; }; struct bridges //桥 { int from,to; }; int times=0; int num_bridge=0; int block; //时间戳,桥数量,“块”数(边双连通分量数) vector<edges>edge(400001); vector<bridges> ve; void tarjan(int u,int fa) //走一遍,求出桥 { dfn[u]=low[u]=++times; instack[u]=1; s.push(u); //入栈, for(int i=head[u];i!=-1;i=edge[i].pre) { int child=edge[i].to; if(visited[child]==0) { visited[child]=1; tarjan(child,u); low[u]=min(low[u],low[child]); if(dfn[u]<low[child]) //是桥,保存起来 { num_bridge++; bridges temp; temp.from=u;temp.to=child; ve.push_back(temp); } } else if(child!=fa) { low[u]=min(dfn[child],low[u]); } } if(dfn[u]==low[u]) //发现一个边双连通分量 blosk++ { block++; int now=s.top(); while(now!=u) { instack[now]=0; s.pop(); tree[now]=block; now=s.top(); } instack[now]=0; s.pop(); tree[now]=block; } } void dfs(int u,int lev) //走一遍DFS,自制缩成一棵树,用father【i】来连接,规定了方向,并记录每个点深度。 { level[u]=lev; int len=bridge[u].size(); for(int i=0;i<len;i++) { int v=bridge[u][i]; if(mark[v]==0) { father[v]=u; mark[v]=1; dfs(v,lev+1); } } } void lca(int u,int v) //每次询问添加的边,调用一次LCA,将路经上(按father和深度level向上走)的标记为非桥。 { if(level[u]>level[v]){int temp=v;v=u;u=temp;} while(level[v]>level[u]) { if(is_bridge[v]) { num_bridge--; is_bridge[v]=0; } v=father[v]; } while(v!=u) { if(is_bridge[v]) { num_bridge--; is_bridge[v]=0; } if(is_bridge[u]) { num_bridge--; is_bridge[u]=0; } v=father[v]; u=father[u]; } } int main() { int n,m; int tcase=1; while (~scanf("%d%d",&n,&m)&&(n||m)) { for(int i=0;i<100001;i++) { level[i]=mark[i]=tree[i]=father[i]=low[i]=dfn[i]=visited[i]=0; head[i]=-1; bridge[i].clear(); is_bridge[i]=1; } ve.clear(); int a,b; num_bridge=block=times=0; for(int i=0;i<2*m;i++) //读入 { scanf("%d%d",&a,&b); edge[i].to=b; edge[i].pre=head[a]; head[a]=i; i++; edge[i].to=a; edge[i].pre=head[b]; head[b]=i; } visited[1]=1; tarjan(1,-1); for(int i=0;i<ve.size();i++) { bridge[tree[ve[i].from]].push_back(tree[ve[i].to]); bridge[tree[ve[i].to]].push_back(tree[ve[i].from]); } mark[1]=1; dfs(1,0); int que;scanf("%d",&que); printf("Case %d: ",tcase); tcase++; while(que--) { scanf("%d%d",&a,&b); lca(tree[a],tree[b]); printf ("%d ",num_bridge); } printf(" "); } return 0; }
方法3:
#pragma comment(linker, "/STACK:10240000000000,10240000000000")// 申请空间,否则爆。Hdu 460MS ac #include<iostream> //poj 1000MS AC, #include<vector> #include<cstring> #include<cstdio> using namespace std; int dfn[100001]; int low[100001]; bool visited[100001]; //tarjan标记访问 int father[100001]; //缩点后建成一棵树 int head[100001]; //每个边双连通分量中的点属于一个集合,函数值1-block bool is_bridge[100001]; //标记桥,形成树后,用点来标记边也可, int min(int a,int b) { if(a<=b)return a; return b; } struct edges //前向星保存边 { int pre,to; }; int times=0; int num_bridge=0; //时间戳,桥数量, vector<edges>edge(400001); void tarjan(int u,int fa) //走一遍,求出桥,不忘了。dfs生成的是树!所以可以标记后面一个点来标记边!无向图tarjan俩个变量 { dfn[u]=low[u]=++times; for(int i=head[u];i!=-1;i=edge[i].pre) { int child=edge[i].to; if(visited[child]==0) //理解这里是树枝边(向前)! { visited[child]=1; father[child]=u; //生成树的父亲 tarjan(child,u); low[u]=min(low[u],low[child]); if(dfn[u]<low[child]) //是桥,保存起来,标记后一个点即可(生成树每个点对应一条到它的边) { num_bridge++; is_bridge[child]=1; } } else if(child!=fa) //返祖边(向后边)点已经访问,说明child是u的某个祖先! { low[u]=min(dfn[child],low[u]); } } } void lca(int u,int v) //每次询问添加的边,调用一次LCA,将路经上(按father和dfn向上走)的标记为非桥。dfn恰好是树的的层次。 { if(dfn[u]>dfn[v]){int temp=v;v=u;u=temp;} while(dfn[v]>dfn[u]) //到同一层为止。 { if(is_bridge[v]) { num_bridge--; is_bridge[v]=0; } v=father[v]; } while(v!=u) //到公共祖先为止。 { if(is_bridge[v]) { num_bridge--; is_bridge[v]=0; } if(is_bridge[u]) { num_bridge--; is_bridge[u]=0; } v=father[v]; u=father[u]; } } int main() { int n,m; int tcase=1; while (~scanf("%d%d",&n,&m)&&(n||m)) { for(int i=0;i<100001;i++) { dfn[i]=father[i]=low[i]=dfn[i]=visited[i]=0; head[i]=-1; is_bridge[i]=0; } int a,b; num_bridge=times=0; for(int i=0;i<2*m;i++) //读入 { scanf("%d%d",&a,&b); edge[i].to=b; edge[i].pre=head[a]; head[a]=i; i++; edge[i].to=a; edge[i].pre=head[b]; head[b]=i; } visited[1]=1; tarjan(1,-1); int que;scanf("%d",&que); printf("Case %d: ",tcase); tcase++; while(que--) { scanf("%d%d",&a,&b); lca(a,b); printf ("%d ",num_bridge); } printf(" "); } return 0; }