HDU-4612 Warm up,tarjan求桥缩点再求树的直径!注意重边
来源:互联网 发布:js手机版 下拉框 编辑:程序博客网 时间:2024/06/06 16:50
Warm up
虽然网上题解这么多,感觉写下来并不是跟别人竞争访问量的,而是证明自己从前努力过,以后回头复习参考!
题意:n个点由m条无向边连接,求加一条边后桥的最少数量。
思路:如标题,tarjan算法求出所有的桥,然后连通的缩成点,用桥连接这些点,很容易发现这是一颗生成树,我们再加一条边必然成环,要使得桥的数量最少,就得使得这个环中的边最多。于是找这棵树最长的一条链。即树的直径。然后桥的数量减去直径既是答案。为什么不用加一呢,因为加的那条边使得成环不算桥。
总结:此题做了两天,如果全力去做的话估计的一天,不算上课和其中做的题。还不是自己做的,对,参考了网上的很多博客,不过这不是重点,学到东西才是王道。
一、看懂题后知道要缩点也知道要求最长的那条链,但我并没有做过求树的直径,于是在百度了树的直径。很多博客并没有给出详细的证明。于是花了点时间理解。
二、鉴于此题有重边问题,于是和WR交流了一下,我想着怎么快速hash一下处理重边,然而数据范围可能至使超int,用map函数可能也有超时,不然数据小都用领接矩阵这不水题了。WR说领接表重边没关系,于是我放弃了惯用的vector,对于领接表生疏的原因是初学的时候能力欠缺,晦涩难懂,后来接触了vector几乎没用过领接表。其实对于他们的差别我还是很清楚的,不过出题方应该不会卡这点时间。然后参考者网上的代码修修改改A了,突然意识到代码这和vector式没多大差别,然后打算用vector来一发,不负众望,果断WA。。。。不过已经找到了一组数据debug。
const int N=2e5+10;int d,key,c,ti,tot,top,bridge,n,m;int head[N],low[N],dfn[N],Stack[N],belong[N],vis[N];vector<int>g[N];struct node{ int to,next,f;} e[N*10];void init(){ c=bridge=ti=top=tot=0; for(int i=0; i<N; i++) { head[i]=-1; Stack[i]=low[i]=dfn[i]=0; belong[i]=vis[i]=0; g[i].clear(); } memset(e,0,sizeof(e));}void add(int u,int v){ e[tot].to=v; e[tot].next=head[u]; head[u]=tot++;}void tarjan(int u){ int v; low[u]=dfn[u]=++ti; vis[u]=1; Stack[top++]=u; for(int i=head[u]; i+1; i=e[i].next) { v=e[i].to; if(e[i].f) continue; e[i].f=e[i^1].f=1; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) bridge++; } else if(vis[v]&&dfn[v]<low[u]) low[u]=dfn[v]; } if(dfn[u]==low[u]) { c++; do { v=Stack[--top]; vis[v]=0; belong[v]=c; } while(v!=u); }// puts("1");}void dfs(int k,int pre,int dep){ vis[k]=1; if(dep>d) { d=dep; key=k; } for(int i=0; i<g[k].size(); i++) { int v=g[k][i]; if(!vis[v]) dfs(v,k,dep+1); }}void work(){ for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i); for(int i=1; i<=n; i++) for(int j=head[i]; j!=-1; j=e[j].next) { int s=belong[i],t=belong[e[j].to]; if(s!=t) { g[s].push_back(t); g[t].push_back(s); }// puts("1"); } d=key=0; memset(vis,0,sizeof(vis)); vis[1]=1; dfs(1,1,0); d=0; memset(vis,0,sizeof(vis)); vis[key]=1; dfs(key,key,0);// printf("%d %d\n",bridge,d); printf("%d\n",bridge-d);}int main(){ while(~scanf("%d%d",&n,&m)) { if(n==m&&m==0) return 0; init(); int u,v; for(int i=0; i<m; i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } work(); } return 0;}//搜索并没有调用栈,于是不担心手动加栈问题。
限于时间问题,明天将vector的问题呈上!
以下呈上上述问题的解答:纯属个人理解,若有不同看法或更高的见解敬请联(教)系(教)我。
上面说了已经有一组样例能推翻vector存图的重边问题了。嘿嘿,是在讨论区看的:
6 6
1 2
2 3
3 4
4 5
2 6
6 2
输出:0。 而不是 1
于是我模拟了一下这组样例终于发现问题所在了,我们可以观察领接表存图的时候相当于把无向图拆分成两个有向图然后建立起来,我们建立的时候可以发现一条无向边存了两次,他们的边的序号是相邻的(参见add()函数),于是在tarjan的时候只需求一次就行了,而另一条i^1只需标记起来即可。但若出现重边,这意味着重边将至少被存4次,我们相邻的可以标记起来,遍历时遇到直接continue,但总是会通过next找到另一个点,这样就形成了一个环了,染色时颜色是一样的。所以巧妙地解决了重边不是桥的问题!
而用vector存图时,我们是用一个father来标记是否访问过的,如果两个点成环即重边出现,我们是通过一个点找到另一个点的,即出发点作为father,那么在遍历邻点的时候不管邻点的领接图里存了多少个相同的father,都会被continue,邻点出发无法再回到father了,和单向边就没有区别了,这样这条边肯定会被当成桥。至于解决容我再三思三思!
给出vector存图的Wrong Answer:上组样例无法通过
const int N=2e5+10;int d,key,c,ti,top,bridge,n,m;int low[N],dfn[N],Stack[N],belong[N],vis[N];vector<int>g[N];vector<int>g1[N];void init(){ c=top=ti=key=d=bridge=0; memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(Stack,0,sizeof(Stack)); memset(vis,0,sizeof(vis)); memset(belong,0,sizeof(vis)); for(int i=0; i<N; i++) { g[i].clear(); g1[i].clear(); }}void tarjan(int u,int pre){ int v; low[u]=dfn[u]=++ti; vis[u]=1; Stack[top++]=u; for(int i=0; i<g[u].size(); i++) { v=g[u][i]; if(v==pre) continue;//当前点无法回到pre点,这条边必然成桥 if(!dfn[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) bridge++; } else if(vis[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { c++; do { v=Stack[--top]; vis[v]=0; belong[v]=c; } while(u!=v); }}void dfs(int k,int dep){ vis[k]=1; if(dep>d) { d=dep; key=k; } for(int i=0; i<g1[k].size(); i++) { int v=g1[k][i]; if(!vis[v]) dfs(v,dep+1); }}void work(){ for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i); for(int i=1; i<=n; i++) for(int j=0; j<g[i].size(); j++) { int v=g[i][j]; if(belong[v]!=belong[i]) { g1[belong[v]].push_back(belong[i]); g1[belong[i]].push_back(belong[v]); } } memset(vis,0,sizeof(vis)); dfs(1,0); memset(vis,0,sizeof(vis)); dfs(key,0); printf("%d %d\n",bridge,d); printf("%d\n",bridge-d);}int main(){ while(~scanf("%d%d",&n,&m),n+m) { init(); int u,v; for(int i=0; i<m; i++) { scanf("%d%d",&u,&v); g[u].push_back(v); g[v].push_back(u); } work(); } return 0;}
- HDU-4612 Warm up,tarjan求桥缩点再求树的直径!注意重边
- hdu 4612 Warm up (带有重边的无向图Tarjan+树的直径)
- HDU 4612 Warm up (边双联通,树的直径)
- HDU 4612 Warm up 边双连通+树的直径
- hdu 4612 Warm up(无向图Tarjan+树的直径)
- hdu 4612 Warm up(无向图Tarjan+树的直径)
- hdu 4612 Warm up 有重边的tarjan
- hdu 4612 Warm up 双连通缩点+树的直径
- Hdu 4612 Warm up (双连通缩点+树的直径)
- HDU 4612 Warm up (树的直径 + 双联通)
- hdu 4612 Warm up(边-双连通+缩点+树的直径)
- HDU 4612 Warm up(边双联通求树的直径)
- HDU 4612--Warm up 【无向图边双连通求桥数 && 缩点后重建图求树的直径】
- HDU 4612 Warm up(边双连通分量+树的直径)
- hdu 4612 Warm up 边双连通分量+树的直径
- HDU 4612 Warm up(边双连通、树的直径)
- hdu 4612 Warm up(缩点+树直径)
- HDU-4612-Warm up(无向图缩点+直径)
- how to debug inputmethod
- Android中巧妙的位运算(Android源码中常见的一些flag的运算的理解)
- Spring实现定时任务方法
- Cookie 和 Session机制详解
- JAVA事务的概念
- HDU-4612 Warm up,tarjan求桥缩点再求树的直径!注意重边
- 关于Java中的内存泄漏问题及注意事项
- SqlServer表与表之间字段一对多sql语句写法
- python, del[] 用法, 笨方法学python
- Java基础学习记录之网络编程(TCP/UDP)
- Spark学习笔记 --- scala实现Spark wordcount例子
- 陆金所抢标攻略
- FloatingSearchView 的使用
- Java语言基础(二)-变量与常量