题意:
给一个无向图..判断这个图中的每个边...any 为其是该图所有最小生成树共有的边...at least one..该边至少为一个最小生成树的边..none..该边不存在该图的任何的最小生成树中..
题解:
那么思考如何判断一个边是某个最小生成树的边...回顾kruskal算法..将所有的边按其长度从小到大排好序..从短边开始连点...连点的同时缩点...用并查集维护...本题本问的原理类似...只是在加边连点的时候一次性将长度相等的且之前两端点不在一个集合的同时做.一起加..一起连..一起缩点....可以想到..如果有几条长度相同的当前可用最小边能够加入现有的图中...由于这几条边都是当前最小..按照传统的kruskal..这几条边都有可以选入最小生成树的...所以这几条边至少应该是at least one...并连点缩点...而后面有较长的边..两端点已经在一个集合..其必定不是最小生成树的边...
第二个问题..如何判断一条边是所有最小生成树都有的...这里利用tarjan算法求无向图的桥...做kruskal时..每轮加相同长度的边连点后(缩点前)..对现有的无向图跑一次tarjan..求出桥..就是any....而tarjan求无向图的桥思路也很简单..从任意点开始dfs..形成一颗搜索树.dfn代表遍历每个点的顺序标号..low代表这个点可以通过其子节点最上可以返回到哪个位置...若不能回到该点或者上方..那么对应的该边必定是桥...也就是对于一条边(s,e)..若low(e)<dfn(s)...则这条边是桥..在一个注意的是由于本题是存在重边的..如果直接判断不能回到其父亲会出错..所以要改为不沿原路走回去...
最后回到问题的本质...如果一个边是at least one..说明其在做kruskal时..至少有一个和其长度相同的边两端连了与其两端相同的集合...如果一个边是any..说明其在做kruskal时..与其长度相同的边只有它自己一个两端连了这两个集合... 第一个样例就很好的说明了这个意思...
本题的基本思路绕了很多..说白了..和kruskal没区别..可以想象..若每条边长度都不同..找at least one就是裸的kruskal...如果有多个相同的边..要一起加的原因是有可能两个长度相同的边连接了同两个集合..如果直接kruskal就只会找出条其中一条边了...但根据题意这样这两条边都是at least one的..每次处理完一组长度相同的边..留下的就是一堆点的集合...当前的点集状态和做裸的kruskal做到当前边的状态是完全相同的...后面的边在这个基础上再处理..
Program:
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<queue> #include<stack> #include<set> #include<algorithm> #define ll long long #define oo 1000000007 #define pi acos(-1.0) #define MAXN 100005 using namespace std; struct node { int x,y,l,id,next; bool operator <(node a) const { return l<a.l; } }line[2*MAXN],Dline[MAXN]; int n,m,_next[MAXN],father[MAXN],ans[MAXN],dfn[MAXN],low[MAXN],_DfsIndex; void addline(int x,int y,int l,int id,int m) { line[m].next=_next[x],_next[x]=m; line[m].x=x,line[m].y=y,line[m].l=l,line[m].id=id; } int getfather(int x) { if (father[x]==x) return x; return father[x]=getfather(father[x]); } void tarjan(int x,int f) { int k,y; dfn[x]=low[x]=++_DfsIndex; for(k=_next[x];k;k=line[k].next) if(line[k].id!=f) //id标记的路..不走原路回去 { y=line[k].y; if(!dfn[y]) { tarjan(y,line[k].id); low[x]=min(low[x],low[y]); if (low[y]>dfn[x]) ans[line[k].id]=2; //是桥.是any }else low[x]=min(low[x],dfn[y]); } return; } void Kruskal() { int i,x,s,e,LineNum=0; sort(Dline+1,Dline+1+m); //边按长度排序 memset(dfn,0,sizeof(dfn)); memset(ans,0,sizeof(ans)); memset(_next,0,sizeof(_next)); for (i=1;i<=n;i++) father[i]=i; _DfsIndex=0; x=1; while (x<=m) { s=x; int fx,fy; for (e=s;e<=m;e++) if (Dline[e].l!=Dline[s].l) break; //找出一组相同的..s为头..e为尾 e--; for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); if (fx==fy) continue; ans[Dline[i].id]=1; //肯定是在某个最小生成树中了 addline(fx,fy,Dline[i].l,Dline[i].id,++LineNum); addline(fy,fx,Dline[i].l,Dline[i].id,++LineNum); //加边 } for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); if (fx==fy) continue; tarjan(fx,0); father[fx]=fy; //缩点 } LineNum=0; for (i=s;i<=e;i++) { fx=getfather(Dline[i].x),fy=getfather(Dline[i].y); dfn[fx]=dfn[fy]=_next[fx]=_next[fy]=0; //去边 } x=e+1; } } int main() { int i; while (~scanf("%d%d",&n,&m)) { for (i=1;i<=m;i++) { int x,y,l; scanf("%d%d%d",&x,&y,&l); Dline[i].x=x,Dline[i].y=y,l,Dline[i].l=l,Dline[i].id=i; } Kruskal(); for (i=1;i<=m;i++) if (!ans[i]) printf("none\n"); else if (ans[i]==1) printf("at least one\n"); else printf("any\n"); } return 0; }