NOIP2013 D1T3 货车运输 倍增LCA OR 并查集按秩合并
2016-07-13 07:54
549 查看
思路:
Kruskal求最大生成树+倍增LCA
队长讲了还有一中很奇怪的方法可以乱搞。
就是:Bling 并查集!
我们可以想到Kruskal进行的过程中是把两个连通块连起来,中间连的边一定比连通块里面的边要小。
那么我们可以考虑按秩合并。。可以证明这样树的高度是log的。
然后直接暴力求LCA即可
网上是这么说的:
启发式并查集,就是维护每个集合的深度,在合并两个集合的时候把小的那个集合挂在大集合下。
在此题中呢,求最大生成树的同时,不把新加入的一条边作为计算答案的树,而是把两个集合的祖先加入树中,边权就是原来边的两个边权。看到这,不禁产生了疑问,树的边权和形态与求出的最大生成树都不一样,为啥能做???其实没有关系,因为新加入的边不影响
原来集合中两点的答案,合并的两个集合中的点合并后肯定要经过原来这条边,那我把祖先接起来用原来边的边权也是一样的。
但是这么做,由于使用了启发式合并,那么最后新的树高度可以证明不会超过logn(其实我也不会证大笑),那么我们不用倍增处理这棵树,直接暴力求lca即可,不仅代码短,而且常数小!!!
Kruskal求最大生成树+倍增LCA
// by SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 105000 int n,m,tot=0,xx,yy,zz,ans; int first[N],v[N*10],next[N*10],w[N*10],f[N],dep[N],fa[N][20],minn[N][20]; int find(int x){return x==f[x]?x:f[x]=find(f[x]);} struct EDGE{int from,to,weight;}Edge[50500]; void add(int x,int y,int z){ w[tot]=z,v[tot]=y; next[tot]=first[x]; first[x]=tot++; } bool cmp(EDGE x,EDGE y){return x.weight>y.weight;} void dfs(int x){ for(int j=1;j<=18;j++){ fa[x][j]=fa[fa[x][j-1]][j-1]; minn[x][j]=min(minn[x][j-1],minn[fa[x][j-1]][j-1]); } for(int i=first[x];~i;i=next[i]) if(dep[v[i]]==-1){ dep[v[i]]=dep[x]+1; fa[v[i]][0]=x;minn[v[i]][0]=w[i]; dfs(v[i]); } } int lca(int x,int y){ int ans=0x3fffffff; if(dep[x]<dep[y])swap(x,y); for(int i=18;i>=0;i--)if(dep[x]>=dep[y]+(1<<i))ans=min(ans,minn[x][i]),x=fa[x][i]; if(x==y)return ans; for(int i=18;i>=0;i--) if(fa[x][i]!=fa[y][i]){ ans=min(ans,min(minn[x][i],minn[y][i])); x=fa[x][i];y=fa[y][i]; } return min(ans,min(minn[x][0],minn[y][0])); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)f[i]=i; memset(dep,-1,sizeof(dep)); memset(minn,0x3f,sizeof(minn)); memset(first,-1,sizeof(first)); for(int i=1;i<=m;i++){ scanf("%d%d%d",&xx,&yy,&zz); Edge[i].from=xx;Edge[i].to=yy;Edge[i].weight=zz; } sort(Edge+1,Edge+1+m,cmp); for(int i=1;i<=m;i++) if(find(Edge[i].from)!=find(Edge[i].to)){ f[find(Edge[i].from)]=find(Edge[i].to); add(Edge[i].from,Edge[i].to,Edge[i].weight); add(Edge[i].to,Edge[i].from,Edge[i].weight); } dep[find(1)]=0;dfs(find(1)); scanf("%d",&m); while(m--){ scanf("%d%d",&xx,&yy); if(~dep[xx]&&~dep[yy])printf("%d\n",lca(xx,yy)); else puts("-1"); } }
队长讲了还有一中很奇怪的方法可以乱搞。
就是:Bling 并查集!
我们可以想到Kruskal进行的过程中是把两个连通块连起来,中间连的边一定比连通块里面的边要小。
那么我们可以考虑按秩合并。。可以证明这样树的高度是log的。
然后直接暴力求LCA即可
网上是这么说的:
启发式并查集,就是维护每个集合的深度,在合并两个集合的时候把小的那个集合挂在大集合下。
在此题中呢,求最大生成树的同时,不把新加入的一条边作为计算答案的树,而是把两个集合的祖先加入树中,边权就是原来边的两个边权。看到这,不禁产生了疑问,树的边权和形态与求出的最大生成树都不一样,为啥能做???其实没有关系,因为新加入的边不影响
原来集合中两点的答案,合并的两个集合中的点合并后肯定要经过原来这条边,那我把祖先接起来用原来边的边权也是一样的。
但是这么做,由于使用了启发式合并,那么最后新的树高度可以证明不会超过logn(其实我也不会证大笑),那么我们不用倍增处理这棵树,直接暴力求lca即可,不仅代码短,而且常数小!!!
// by SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 105000 int n,m,tot=0,xx,yy,zz; int first ,v[N*10],next[N*10],w[N*10],f ,dep ,fa ,size ,minn ; int find(int x){return x==f[x]?x:f[x]=find(f[x]);} struct EDGE{int from,to,weight;}Edge[50500]; void add(int x,int y,int z){w[tot]=z,v[tot]=y;next[tot]=first[x];first[x]=tot++;} bool cmp(EDGE x,EDGE y){return x.weight>y.weight;} void dfs(int x){ for(int i=first[x];~i;i=next[i]) if(dep[v[i]]==-1){ dep[v[i]]=dep[x]+1; fa[v[i]]=x;minn[v[i]]=w[i]; dfs(v[i]); } } void lca(int x,int y){ int ans=0x3fffffff; if(dep[x]>dep[y])swap(x,y); while(dep[x]!=dep[y])ans=min(minn[y],ans),y=fa[y]; while(x!=y){ ans=min(ans,min(minn[x],minn[y])); x=fa[x];y=fa[y]; } printf("%d\n",ans); return; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)size[i]=1; for(int i=1;i<=n;i++)f[i]=i; memset(dep,-1,sizeof(dep)); memset(first,-1,sizeof(first)); memset(minn,0x3f,sizeof(minn)); for(int i=1;i<=m;i++){ scanf("%d%d%d",&xx,&yy,&zz); Edge[i].from=xx;Edge[i].to=yy;Edge[i].weight=zz; } sort(Edge+1,Edge+1+m,cmp); for(int i=1;i<=m;i++){ int fx=find(Edge[i].from),fy=find(Edge[i].to); if(fx!=fy){ if(size[fx]>size[fy])swap(fx,fy); f[fx]=fy;size[fy]+=fx; add(fx,fy,Edge[i].weight);add(fy,fx,Edge[i].weight); } } dep[find(1)]=0;dfs(find(1)); scanf("%d",&m); while(m--){ scanf("%d%d",&xx,&yy); if(~dep[xx]&&~dep[yy])lca(xx,yy); else puts("-1"); } }
相关文章推荐
- Flex Namespace的用法
- ajax使用不同namespace的action的方法
- 浅谈几种常见语言的命名空间(Namespace)
- thinkphp autoload 命名空间自定义 namespace
- PHP命名空间(namespace)的使用基础及示例
- php中namespace use用法实例分析
- 浅析JavaScript中命名空间namespace模式
- C++ namespace相关语法实例分析
- PHP命名空间(Namespace)简明教程
- PHP命名空间(Namespace)的使用详解
- JavaScript创建命名空间(namespace)的最简实现
- ASP.Net中命名空间Namespace浅析和使用例子
- C++ 匿名namespace的作用以及它与static的区别
- YY再现淫秽内容,在线直播平台成下一个快播?
- 浅析JavaScript中命名空间namespace模式
- YY、映客、陌陌、KK、斗鱼五大直播阵营即将形成?
- 这个小例子也许能帮助大家理解一下SIGUSR1的用法
- 不要在头文件中使用 using
- 少了一个发夹
- Introducing Linux Network Namespaces