hdu 3394
题意:有一个公园有n个景点,公园的管理员准备修建m条道路,并且把参观路线安排成回路。如果一条道路被多条参观路线(也就是这个点连通分量含多个环)公用,那么这些参观路线中会有冲突;如果一条道路没在任何一个回路内(这个道路就是桥),那么这条路是不冲突的。问分别有多少条有没有冲突的路和有冲突的路(从例子里看含多个环的点双连通分量的全部边都是有冲突的)。
思路:
要形成环是求点的双连通分量,这样才满足只有一个环的双连通分量中的边数等于点数。
在点双连通分量中:
当点数=边数,形成一个环
当点数>边数(一条线段,说明这条边是桥)
当点数<边数,那么就含1个以上的环了
特殊情况,假设只有一条边a----b,a是顶点,不特判顶点会把a误认为是割点(a---b是割边倒没错,不会改变第一个答案),于是a-b就是点双连通分量,但是程序会算出这个双连通分量的边数和点数都是1,也就是一个桥,不会改变第二个答案,所以找割点时不特判是不是顶点不会影响结果。
桥的个数就是割边的数量,有冲突的路就是所有边数大于点数的点双连通分量的边数总和
用栈存储每个访问到的点,当找到割点也就是时,已经访问完一个极大点双连通分量的访问,只需要将栈内的边一直出栈直到(u,v)出栈就停止。这些边都是这个极大点双连通分量的边。
#include<bits/stdc++.h> using namespace std; const int maxn=1e4+5,maxm=2e5+5; int head[maxn],Next[maxm],to[maxm],tot; int ans1,ans2;//输出的结果 int low[maxn],num[maxn],dfn; bool vis[maxn]; void add(int x,int y) { to[++tot]=y; Next[tot]=head[x]; head[x]=tot; } struct Node{ int x,y; }node; stack<Node>st; void tarjan(int u) { int x,y; int res=0,ans=0; memset(vis,false,sizeof vis); while(!st.empty()) { node=st.top(); st.pop(); x=node.x,y=node.y; ++ans;//该点连通分量中的边数 if(!vis[y]) { ++res;//该点连通分量中的点数 vis[y]=true; } if(x==u) break; if(!vis[x]) { ++res; vis[x]=true; } } if(ans>res) ans2+=ans; } void dfs(int u,int fa) { low[u]=num[u]=++dfn; for(int i=head[u];i;i=Next[i]) { int v=to[i]; if(v==fa) continue; if(!num[v]) { st.push(Node{u,v}); dfs(v,u); low[u]=min(low[u],low[v]); if(num[u]<=low[v]) { if(num[u]<low[v]) ++ans1;//割边的判定条件 tarjan(u); }//判断割点和割边需要u!=1,但我们需要的是桥和含多个环的点连通分量 }//ans1一定是桥的数量,如果不是割点那么不会影响ans2的值 else if(num[v]<num[u]) { st.push(Node{u,v}); low[u] = min(low[u], num[v]); }//有回退边 } } int main() { int n,m; while(~scanf("%d%d",&n,&m)&&n) { memset(head,0,sizeof head); memset(num,0,sizeof num); for(int i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); ++u,++v; add(u,v),add(v,u); } tot=dfn=ans1=ans2=0; for(int i=1;i<=n;++i) if(!num[i]) dfs(i,-1); printf("%d %d\n",ans1,ans2); } return 0; }
hdu 3749
。
思路:两个点不在同一个连通块内就没有路径,点双连通分量中的任意两个点之间至少有两条路径(只有两个点的时候只有一条路径),不在同一个点双连通分量中两个点只有一条路径。并查集判断是否连通,存第cnt个点双连通分量中的所有点,存所有包含点u的点双连通分量,判断是否在同一个点双连通分量内,判断点双连通分量是否不止两个点。
我在这题的讨论里还发了一些数据,就通过这些数据找到了自己的bug,我的代码中不能偷懒不设cnt给点双连通分量分“座位”直接用dfn来代替这个点双连通分量的编号(dfn是割点的编号),因为这个割点可能还是别的点双连通分量的割点,会出现将两个不同的点双连通分量合并的情况。
Code:
#include<bits/stdc++.h> using namespace std; const int maxn=5e3+50,maxm=2e4+50; int head[maxn],Next[maxm],to[maxm],tot,all; int fa[maxn]; int low[maxn],num[maxn],dfn; int vis[maxn]; vector<int> a[maxn],b[maxn]; void add(int x,int y) { to[++tot]=y; Next[tot]=head[x]; head[x]=tot; } struct Node{ int x,y; }node; stack<Node>st; int find(int x) { return fa[x]==-1?x:(fa[x]=find(fa[x])); } void tarjan(int u,int cnt) { int x,y; a[cnt].clear(); while(!st.empty()) { node=st.top(); st.pop(); x=node.x,y=node.y; if(vis[y]!=cnt) { vis[y]=cnt; a[cnt].push_back(y); b[y].push_back(cnt); } if(x==u) break; if(vis[x]!=cnt) { vis[x]=cnt; a[cnt].push_back(x); b[x].push_back(cnt); } } } void dfs(int u,int fa) { low[u]=num[u]=++dfn; for(int i=head[u];i;i=Next[i]) { int v=to[i]; if(v==fa) continue; if(!num[v]) { st.push(Node{u,v}); dfs(v,u); low[u]=min(low[u],low[v]); if(num[u]<=low[v]) tarjan(u,++all); } else if(num[v]<num[u]) { st.push(Node{u,v}); low[u] = min(low[u], num[v]); }//有回退边 } } int main() { int n,m,q,cas=0; while(~scanf("%d%d%d",&n,&m,&q)&&n) { memset(head,0,sizeof head); memset(vis,0,sizeof vis); memset(num,0,sizeof num); memset(fa,-1,sizeof fa); for(int i=0;i<=n;++i) b[i].clear(); for(int i=1;i<=m;++i) { int u,v,x,y; scanf("%d%d",&u,&v); ++u,++v; add(u,v),add(v,u); x=find(u),y=find(v); if(x!=y) fa[y]=x; } tot=dfn=all=0; for(int i=1;i<=n;++i) if(!num[i]) dfs(i,-1); printf("Case %d:\n",++cas); //for(int i=1;i<=all;++i) printf("%d ",a[i].size()); //puts(""); for(int i=1;i<=q;++i) { int u,v; scanf("%d%d",&u,&v); ++u,++v; if(find(u)!=find(v)) puts("zero"); else { bool flag=true; for(auto i:b[u]) for(auto j:b[v]) if(i==j) { //printf("i==%d j==%d\n",i,j); if(a[i].size()>2) puts("two or more"),flag=false; } if(flag) puts("one"); } } } return 0; }
hdu 2460
思路:
1.求出原图中桥的数量,存入每个点的父亲(fa[v]=u),于是可以通过标记v来表示u--v是否是割边。
2.每添加一条边后求出桥减少的数量
3.原来的-减少=答案
桥的数量就是割边的数量,桥就是两个边双连通分量之间的边。把一个边双连通分量中的所有点看成一个点(缩点),那么原图就成了一颗树,在两个点之间添加一条边就是在两个边双连通分量(可以是同一个边双连通分量)之间添加一条边。在u和v之间添加一条边,u到LCA(u,v)和v到LCA(u,v)路径上的所有边都会合成同一个边双连通分量,桥减少的数量就是路径上桥的数量。
Code:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+7,maxm=4e5+7; int head[maxn],Next[maxm],to[maxm],tot; int low[maxn],num[maxn],dfn; int fa[maxn],cnt,sum; bool is_brige[maxn]; void add(int x,int y) { to[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void tarjan(int u,int f) { fa[u]=f; low[u]=num[u]=++dfn; for(int i=head[u];i;i=Next[i]) { int v=to[i]; if(v==f) continue; if(!num[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(num[u]<low[v]) ++cnt,is_brige[v]=true; } else if(low[u]>num[v]) low[u] = num[v]; } } int main() { int n,m,q,cas=0; while(~scanf("%d%d",&n,&m)&&n) { memset(head,0,sizeof head); memset(is_brige,false,sizeof is_brige); memset(num,0,sizeof num); for(int i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); add(u,v),add(v,u); } tot=dfn=cnt=0; for(int i=1;i<=n;++i) tarjan(1,0); scanf("%d",&q); printf("Case %d:\n",++cas); for(int i=1;i<=q;++i) { int u,v; scanf("%d%d",&u,&v); while(u!=v) { if(is_brige[u]) { is_brige[u]=false; --cnt; } if(is_brige[v]) { is_brige[v]=false; --cnt; } u=fa[u],v=fa[v]; } printf("%d\n",cnt); } puts(""); } return 0; }
hdu 4587
思路:首先想到删掉割点会将一个连通块分成多个连通块,如果都删割点不一定是最优的(如图,删除a、b只只会产生一个连通分量,如果删掉一个d在删掉b就是最优的了)。
枚举第一个删掉的点,第二个删掉割点,得到的结果一定是最优的。
枚举第一个删掉的点后跑一遍图,就以点为根结点跑这个连通分量,同时,left表示删掉第一个结点后连通分量的总数,跑图时用标记割点孩子的数量,删除的第二个结点应该是。每次枚举完的结果就是。+1是删掉割点,祖先也会独立成一个连通分量,根结点没有父亲(默认它是割点,其实如果它只有一个孩子就不是,这里不作区别),所以要预处理一下,-1是因为第二个删掉割点产生个连通分量的前提是left里的一个连通分量被分解。
注意:当只有两个点时或者删掉的第一个结点的孩子都是叶子结点时,,如果(x是第一个删掉的点)就会影响结果。
Code:
#include<bits/stdc++.h> using namespace std; const int maxn=5e3+7,maxm=1e4+7; int head[maxn],Next[maxm],to[maxm],tot; int num[maxn],dfn; int iscut[maxn],none,res,now_left,ans,n,m; void add(int x,int y) { to[++tot]=y; Next[tot]=head[x]; head[x]=tot; } int tarjan(int u,int fa) { int lowu=num[u]=++dfn; for(int i=head[u];i;i=Next[i]) { int v=to[i]; if(v==fa||v==none) continue; if(!num[v]) { int lowv=tarjan(v,u); lowu=min(lowu,lowv); if(num[u]<=lowv) ++iscut[u]; } else if(lowu>num[v]) lowu = num[v]; } return lowu; } int solve(int x) { memset(iscut,0,sizeof iscut); memset(num,0,sizeof num); none=x,iscut[x]=-1,dfn=now_left=res=0; for(int i=1;i<=n;++i) if(i!=x&&!num[i]) --iscut[i],++now_left,tarjan(i, -1); for(int i=1;i<=n;++i) res=max(res,iscut[i]+1); return res+now_left-1; } int main() { while(~scanf("%d%d",&n,&m)) { memset(head,0,sizeof head); ans=tot=0; for(int i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); ++u,++v; add(u,v),add(v,u); } for(int i=1;i<=n;++i) ans=max(ans,solve(i)); printf("%d\n",ans); } return 0; }