D5 LCA 最近公共祖先
第一题: POJ 1330 Nearest Common Ancestors POJ 1330
这个题可不是以1为根节点,不看题就会一直wa呀;
加一个找根节点的措施;
#include<algorithm> #include<bitset> #include<cctype> #include<cerrno> #include<clocale> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque> #include<exception> #include<fstream> #include<functional> #include<limits> #include<list> #include<map> #include<iomanip> #include<ios> #include<iosfwd> #include<iostream> #include<istream> #include<ostream> #include<queue> #include<set> #include<sstream> #include<stack> #include<stdexcept> #include<streambuf> #include<string> #include<utility> #include<vector> #include<cwchar> #include<cwctype> #define N 50010 using namespace std; int f[N][20],d[N],dist[N],lin[N*2],b[N],root; inline int read() { int s = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } struct gg { int y,v,next; }a[N<<1]; int T,n,m,tot,t; queue<int> q; void add(int x,int y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } void bfs(int x) { q.push(x);d[x]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; if(d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for(int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } int lca(int x,int y) { if(d[x]>d[y]) swap(x,y); for(int i=t;i>=0;i--) if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int main() { T=read(); while(T--) { memset(b,0,sizeof(b)); memset(f,0,sizeof(f)); queue<int> q; n=read(); t=(int)(log(n)/log(2))+1; for(int i=1;i<=n;i++) lin[i]=d[i]=0; tot=0; for(int i=1;i<n;i++) { int x,y,z; x=read();y=read(); b[y]++; add(x,y); } for(int i=1;i<=n;i++) { if(b[i]==0) { root=i; break; } } bfs(root); int x,y; x=read();y=read(); cout<<lca(x,y)<<endl; } return 0; }
第二题:HDU 2586
加了边权的lca模板;
关键最近距离为dis【i】+dis【j】-2*dis【lca(x,y)】;
#include<algorithm> #include<bitset> #include<cctype> #include<cerrno> #include<clocale> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque> #include<exception> #include<fstream> #include<functional> #include<limits> #include<list> #include<map> #include<iomanip> #include<ios> #include<iosfwd> #include<iostream> #include<istream> #include<ostream> #include<queue> #include<set> #include<sstream> #include<stack> #include<stdexcept> #include<streambuf> #include<string> #include<utility> #include<vector> #include<cwchar> #include<cwctype> #define N 50010 using namespace std; int f[N][20],d[N],dist[N],lin[N*2]; inline int read() { int s = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } struct gg { int y,v,next; }a[N<<1]; int T,n,m,tot,t; queue<int> q; void add(int x,int y,int z) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; a[tot].v=z; } void bfs() { q.push(1);d[1]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; if(d[y]) continue; d[y]=d[x]+1; dist[y]=dist[x]+a[i].v; f[y][0]=x; for(int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } int lca(int x,int y) { if(d[x]>d[y]) swap(x,y); for(int i=t;i>=0;i--) if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int main() { T=read(); while(T--) { queue<int> q; n=read();m=read(); t=(int)(log(n)/log(2))+1; for(int i=1;i<=n;i++) lin[i]=d[i]=0; tot=0; for(int i=1;i<n;i++) { int x,y,z; x=read();y=read();z=read(); add(x,y,z);add(y,x,z); } bfs(); for(int i=1;i<=m;i++) { int x,y; x=read();y=read(); cout<<dist[x]+dist[y]-2*dist[lca(x,y)]<<endl; } } return 0; }
第三题:BZOJ 1787
对三个节点两两求LCA会有2种情况: 1 均相同:答案即为此LCA: 2 有1个LCA与其他的不同:答案为此LCA。
#include<bits/stdc++.h> #define N 500001 using namespace std; int f[N][20],d[N],dist[N],lin[N*2],ans; inline int read() { int s = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } struct gg { int y,v,next; }a[N<<1]; int T,n,m,tot,t; queue<int> q; void add(int x,int y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } void bfs() { q.push(1);d[1]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; if(d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for(int j=1;j<=t;j++) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } int lca(int x,int y) { ans=0; if(d[x]>d[y]) swap(x,y); for(int i=t;i>=0;i--) if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return ans=f[x][0]; } int dis(int x,int y) { int t=lca(x,y); return d[x]+d[y]-2*d[t]; } int main() { queue<int> q; n=read();m=read(); t=(int)(log(n)/log(2))+1; for(int i=1;i<=n;i++) lin[i]=d[i]=0; tot=0; for(int i=1;i<n;i++) { int x,y; x=read();y=read(); add(x,y);add(y,x); } bfs(); for(int i=1;i<=m;i++) { int x,y,z; x=read();y=read();z=read(); int xx,yy,zz; int p1=lca(x,y),p2=lca(x,z),p3=lca(y,z),t; if(p1==p2) t=p3; else if(p2==p3) t=p1; else t=p2; int ans=dis(x,t)+dis(y,t)+dis(z,t); cout<<t<<' '<<ans<<endl; } return 0; }
来自石神的代码;跑的很快,推荐;
#include<bits/stdc++.h> using namespace std; const int maxn=5e5+10; const int inf=0x7fffffff; template<typename T>inline void read(T &x) { x=0; T f=1,ch=getchar(); while (!isdigit(ch)) ch=getchar(); if (ch=='-') f=-1, ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int ver[maxn<<1],Next[maxn<<1],head[maxn],len; inline void add(int x,int y) { ver[++len]=y,Next[len]=head[x],head[x]=len; } queue<int>q; int d[maxn],f[maxn][21],vis[maxn],t; inline void bfs(int root) { q.push(root); d[root]=1; while (!q.empty()) { int x=q.front(); q.pop(); for (int i=head[x];i;i=Next[i]) { int y=ver[i]; if (d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for (int j=1;j<=t;++j) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } inline int lca(int x,int y) { if (d[x]>d[y]) swap(x,y); for (int i=t;i>=0;--i) if (d[f[y][i]]>=d[x]) y=f[y][i]; if (x==y) return x; for (int i=t;i>=0;--i) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int main() { int n,m,id,ans;read(n);read(m); t=log2(n*1.0); memset(d,0,sizeof(d)); memset(f,0,sizeof(f)); memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); len=0; for (int i=1;i<n;++i) { int x,y;read(x);read(y); add(x,y);add(y,x); } bfs(1); for (int i=1;i<=m;++i) { int x,y,z;read(x);read(y);read(z); int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),ans=inf,tmp,id; int q1=lca(l1,z),q2=lca(l2,y),q3=lca(l3,x); tmp=d[x]+d[y]-d[l1]+d[z]-(d[q1]<<1); if (tmp<ans) ans=tmp,id=l1; tmp=d[x]+d[z]-d[l2]+d[y]-(d[q2]<<1); if (tmp<ans) ans=tmp,id=l2; tmp=d[y]+d[z]-d[l3]+d[x]-(d[q3]<<1); if (tmp<ans) ans=tmp,id=l3; printf("%d %d\n",id,ans); } return 0; }
第四题:UVA11354 Bond
一类例题:LUOGU UVA 11354 最小生成树+倍增求LCA
NOIP 2013 货车运输 最大生成树+倍增求LCA
这个题:最小瓶颈树。 倍增维护边权最大值。
一份代码解决两个问题,代码注释的代码改一改;
#include<bits/stdc++.h> using namespace std; const int maxn=6e5+10; const int inf=0x3f3f3f3f; template<typename T>inline void read(T &x) { x=0; T f=1,ch=getchar(); while (!isdigit(ch) && ch^'-') ch=getchar(); if (ch=='-') f=-1, ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } struct Edge { int x,y,z,next; }G[maxn<<1],A[maxn<<1];//G[]是最大生成树的图 int n,m,head[maxn],len; inline void add(int x,int y,int z) { G[++len].y=y,G[len].z=z,G[len].next=head[x],head[x]=len; } int fa[maxn]; inline int get(int x) { if (x==fa[x]) return x; return fa[x]=get(fa[x]); } inline bool cmp(Edge a,Edge b) { // return a.z>b.z; return a.z<b.z; } inline void Kruskal() { sort(A+1,A+m+1,cmp); for (int i=1;i<=n;++i) fa[i]=i; for (int i=1;i<=m;++i) { int x=get(A[i].x),y=get(A[i].y); if (x!=y) { fa[y]=x; add(A[i].x,A[i].y,A[i].z); add(A[i].y,A[i].x,A[i].z); } } } int d[maxn],f[maxn][21],w[maxn][21];//fa[]表示并查集中的父节点,f[][]表示树上的父节点,w[][]表示最大载重 inline void dfs(int x) { for (int i=1;i<=20;++i)//LCA初始化 { f[x][i]=f[f[x][i-1]][i-1]; // w[x][i]=min(w[x][i-1],w[f[x][i-1]][i-1]); w[x][i]=max(w[x][i-1],w[f[x][i-1]][i-1]); } for (int i=head[x];i;i=G[i].next) { int y=G[i].y; if (d[y]) continue; d[y]=d[x]+1;//计算深度 f[y][0]=x;//储存父节点 w[y][0]=G[i].z;//储存到父节点的权值 dfs(y); } } inline int lca(int x, int y) { if (get(x)!=get(y)) return -1;//不连通,输出-1 // int ans=inf; int ans=0; if (d[x]>d[y]) swap(x,y);//保证y节点更深 for (int i=20;i>=0;--i)//将y节点上提到于x节点相同深度 if (d[f[y][i]]>=d[x]) { // ans=min(ans,w[y][i]);//更新最大载重(最小边权) ans=max(ans,w[y][i]); y=f[y][i];//修改y位置 } if (x==y) return ans;//如果位置已经相等,直接返回答案 for (int i=20;i>=0;--i)//寻找公共祖先 if (f[x][i]!=f[y][i]) { // ans=min(ans,min(w[x][i], w[y][i]));//更新最大载重(最小边权) ans=max(ans,max(w[x][i],w[y][i])); x=f[x][i],y=f[y][i];//修改x,y位置 } // ans=min(ans,min(w[x][0],w[y][0]));//更新此时x,y到公共祖先最大载重,f[x][0], f[y][0]即为公共祖先 ans=max(ans,max(w[x][0],w[y][0])); return ans; } int main() { int flag=0; while (scanf("%d %d",&n,&m)!=EOF) { if (flag) printf("\n"); else flag=1; memset(f,0,sizeof(f)); memset(w,0,sizeof(w)); memset(d,0,sizeof(d)); memset(head,0,sizeof(head)); len=0; for (int i=1;i<=m;++i) read(A[i].x),read(A[i].y),read(A[i].z); Kruskal(); for (int i=1;i<=n;++i)//dfs收集信息 if (!d[i]) { d[i]=1; dfs(i); f[i][0]=i; // w[i][0]=inf; w[i][0]=-inf; } int q; read(q); while (q--) { int x,y; read(x);read(y); printf("%d\n",lca(x,y)); } } return 0; }
第五题:BZOJ 1977 【模板】严格次小生成树[BJWC2010]
先求一次最小生成树,然后枚举哪些非树边,找到以非树边两端点 在树上路径中最大的一条边,将这条边加入树,形成一个环,那么删掉 此环上的一条边,就会从新出现一棵生成树。
如何高效的去找要删去的边?
倍增LCA 倍增维护3个值,father维护LCA,f 维护路径上的最大值,g维护路 径上的严格次大值。
对于删环上的边,如果添加进来的边与环上最大值不同,那么直接 删换上最大值,如果与最大值相同,就必须删次大值。
#include <algorithm> #include <cctype> #include <cmath> #include <complex> #include <cstdio> #include <cstring> #include <deque> #include <functional> #include <list> #include <map> #include <iomanip> #include <iostream> #include <queue> #include <set> #include <stack> #include <string> #include <vector> #define N 100001 #define M 300001 #define inf 0x7fffffff #define ll long long using namespace std; int n,m,tot,cnt,mn=inf; ll ans; int father[N],lin[N],deep[N],f[N][17],d1[N][17],d2[N][17]; inline int read() { int s = 0, w = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); } while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); } return s * w; } void put(int x) { if(x==0){putchar('0');putchar('\n');return;} if(x<0){putchar('-');x=-x;} int num=0;char ch[25]; while(x) ch[++num]=x%10+'0',x/=10; while(num) putchar(ch[num--]); putchar('\n'); } struct data { int x,y,v; bool bl; } a[M]; struct edge { int to,next,v; } e[N*2]; bool cmp(data a,data b) { return a.v<b.v; } void add(int u,int v,int w) { e[++cnt].to=v; e[cnt].next=lin[u]; e[cnt].v=w; lin[u]=cnt; } int find(int x) { return x==father[x]?x:find(father[x]); } void dfs(int x,int fs) { for(int i=1; i<=16; i++) { if(deep[x]<(1<<i)) break; f[x][i]=f[f[x][i-1]][i-1]; d1[x][i]=max(d1[x][i-1],d1[f[x][i-1]][i-1]); if(d1[x][i-1]==d1[f[x][i-1]][i-1]) d2[x][i]=max(d2[x][i-1],d2[f[x][i-1]][i-1]); else { d2[x][i]=min(d1[x][i-1],d1[f[x][i-1]][i-1]); d2[x][i]=max(d2[x][i-1],d2[x][i]); d2[x][i]=max(d2[x][i],d2[f[x][i-1]][i-1]); } } for(int i=lin[x]; i; i=e[i].next) if(e[i].to!=fs) { f[e[i].to][0]=x; d1[e[i].to][0]=e[i].v; deep[e[i].to]=deep[x]+1; dfs(e[i].to,x); } } int lca(int x,int y) { if(deep[x]<deep[y]) swap(x,y); int t=deep[x]-deep[y]; for(int i=0; i<=16; i++) if((1<<i)&t)x=f[x][i]; for(int i=16; i>=0; i--) { if(f[x][i]!=f[y][i]) { x=f[x][i]; y=f[y][i]; } } if(x==y)return x; return f[x][0]; } void cal(int x,int fs,int v) { int mx1=0,mx2=0; int t=deep[x]-deep[fs]; for(int i=0; i<=16; i++) { if(t&(1<<i)) { if(d1[x][i]>mx1) { mx2=mx1; mx1=d1[x][i]; } mx2=max(mx2,d2[x][i]); x=f[x][i]; } } if(mx1!=v)mn=min(mn,v-mx1); else mn=min(mn,v-mx2); } void tp(int t,int v) { int x=a[t].x,y=a[t].y,f=lca(x,y); cal(x,f,v); cal(y,f,v); } int main() { n=read();m=read(); for(int i=1; i<=n; i++) father[i]=i; for(int i=1; i<=m; i++) a[i].x=read(),a[i].y=read(),a[i].v=read(); sort(a+1,a+m+1,cmp); for(int i=1; i<=m; i++) { int p=find(a[i].x),q=find(a[i].y); if(p!=q) { father[p]=q; ans+=a[i].v; a[i].bl=1; add(a[i].x,a[i].y,a[i].v); add(a[i].y,a[i].x,a[i].v); tot++; if(tot==n-1)break; } } dfs(1,0); for(int i=1; i<=m; i++) if(!a[i].bl) tp(i,a[i].v); printf("%lld",ans+mn); return 0; }
第六题:跳跳棋
为了方便研究跳法,我们把棋子按坐标大小排序后设为a, b, c。
每次都有三种跳法: 1 b往左跳 2 b往右跳 3 离b近的往里跳(远的不允许跳,会越过两个棋子)
从只有两种跳法的所有状态出发,就可以到达任意一种状态。
因为 两边往中间跳实际上是一种状态的还原。 对于每一个状态(x, y, z):中间的向外面跳为(2x − y, x, z)或 者(x, z, 2z − y),设为左结点和右结点。
所以我们就可以把题意转化一下,即第一问为两种状态是否有lca, 而第二种则是问两种状态在树上的距离。我们可以采用类似倍增的方法 跳lca,来求出答案。
首先,我们应该判断两个状态可不可以互达。
要做到这一点,实际上就是看两个状态所在树的根是不是相同就行 了。
怎么样算出这个根呢?
令b − a = d1, c − b = d2,不妨设d1 < d2,那么a, b两点可以一直向 右,每次移动d1距离,直到d2 − k × d1 ≤ d1为止,这几乎是一个取模运 算。
1 d1|d2 : 最后剩下d1距离,移动d2|d1 − 1步。
2 剩下d2 mod d1距离,移动[d1\d2|
步。 而根据与其类似的辗转相除的过程的复杂度,这样计算的复杂度 是O(logd)的,这样就能很快算出根了。