LCA问题:最近公共祖先问题
求法主要由:
(1)暴力
(2)倍增法:支持动态
(3)欧拉序列RMQ问题求解(ST表)
(4)tarjin算法
这篇博客写的不错 https://www.cnblogs.com/ljy-endl/p/11349087.html
介绍了树链剖分的算法 (树链剖分详解 https://www.cnblogs.com/IltzInstallBI/p/12744038.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //树链剖分法求LCA int n,m,cnt,son[maxn],size[maxn],top[maxn],fa[maxn],dep[maxn]; int head[maxn],to[maxn*2],nex[maxn*2]; void adde(int u,int v){ to[++cnt]=v; nex[cnt]=head[u]; head[u]=cnt; } void dfs1(int x,int f,int deep){ fa[x]=f; size[x]=1; dep[x]=deep; for(int i=head[x];i;i=nex[i]){ int t=to[i]; if(t==f) continue; dfs1(t,x,deep+1); size[x]+=size[t]; if(!son[x]||size[t]>size[son[x]]) son[x]=t; } } int dfn; void dfs2(int x,int topf){ top[x]=topf; if(!son[x]) return; dfs2(son[x],topf); for(int i=head[x];i;i=nex[i]){ int t=to[i]; if(t==fa[x]||t==son[x]) continue; dfs2(t,t); } } int lca(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; } return dep[x]>dep[y]?y:x; } int dist(int x,int y){ return dep[x]+dep[y]-2*dep[lca(x,y)]; } int main(){ int x,y; cin>>n; for(int i=1;i<n;i++){ cin>>x>>y; adde(x,y); adde(y,x); } dfs1(1,0,1); dfs2(1,1); cin>>m; while(m--){ cin>>x>>y; printf("%d ",dist(x,y)); } return 0; }
这篇博客讲了RMQ的写法,但是我还是觉得倍增好些一点(嘻嘻
https://www.cnblogs.com/zyf0163/p/4859281.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //RMQ算法:求LCA /* 我们将他保存在一个vs数组,并开个id数组记录第一次在vs中出现的下标,例如id[1] = 1, id[4] = 3; 并用dep数组储存vs数组中每个数的深度,例如dep[2] = dep[4] = 1(vs数组中第2个和第4个都是2,2的深度为2)。 而LCA(u, v)就是第一次访问u之后到第一次访问v之前所经过顶点中离根最近的那个。假设id[u] <= id[v],那么LCA(u, v) = vs[t] t为id[u]与id[v]中dep最小的那一个。 而这个不就相当于求区间的RMQ吗? https://www.cnblogs.com/zyf0163/p/4859281.html */ vector<int> g[maxn]; int n,p,rt; int dp[maxn][40],dep[maxn*2-1],id[maxn],summ[maxn]; int vs[maxn*2-1]; struct RMQ{ int lg[maxn]; void inti(int n){ lg[0]=-1; for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1; for(int i=1;i<=n;i++) dp[i][0]=vs[i]; //注意与倍增法里面的倍增数组区别 for(int j=1;j<=lg[n];j++){ for(int i=1;i+(1<<j)-1<=n;i++){ dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } } } int que(int x,int y){ int k=lg[y-x+1]; return min(dp[x][k],dp[y-(1<<k)+1][k]); } }; RMQ rmq; //这个函数很关键 void dfs(int v,int fa,int depth,int &num){ id[v]=num; //dfs序 vs[num]=v; //排第k为的值 dep[num++]=depth; for(int i=0;i<g[v].size();i++){ if(g[v][i]!=fa){ dfs(g[v][i],v,depth+1,num); vs[num]=v; dep[num++]=depth; //最后返回的时候更新 } } } void inti(int v){ int num=1; dfs(rt,-1,0,num); rmq.inti(v*2-1); } int lca(int u,int v){ return vs[rmq.que(min(id[u],id[v]),max(id[u],id[v]))]; } void print(){ for(int i = 0; i < 2 * n; i ++) printf("vs[%d] = %d ", i, vs[i]); for(int i = 1; i <= n; i ++) printf("id[%d] = %d ", i, id[i]); for(int i = 1; i <= n; i ++) printf("dep[%d] = %d ", i, dep[i]); // for(int i = 1; i <= n; i ++) printf("sum[%d] = %d ", i, sum[i]); } int main(){ while(scanf("%d %d",&n,&p)==2){ for(int i=1;i<=n;i++) g[i].clear(); for(int i=1;i<n;i++){ int a,b; scanf("%d %d",&a,&b); g[a].push_back(b); g[b].push_back(a); } rt=1; inti(n); //print(); while(p--){ int a,b; scanf("%d %d",&a,&b); printf("%d ",lca(a,b)); } } return 0; }
一本通上面的题:倍增求LCA
1552:【例 1】点的距离
模板题
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //求树上两点之间的距离: de[x]-de[y]-2de[LCA] //LCA:倍增法求 int n,q; int head[maxn*2],next[maxn*2],to[maxn*2],tot; int de[maxn],f[maxn][21]; //f[i][j],从i开始往上走2^j步能够到达的节点 void adde(int u,int v){ to[++tot]=v; next[tot]=head[u]; head[u]=tot; to[++tot]=u; next[tot]=head[v]; head[v]=tot; } void inti(int u,int fa){ de[u]=de[fa]+1; for(int i=0;i<=19;i++) f[u][i+1]=f[f[u][i]][i]; //求这个表,求深度 for(int i=head[u];i;i=next[i]){ int v=to[i]; if(v==fa) continue; f[v][0]=u; //初始化,走一步是到达爸爸 inti(v,u); } } //用倍增法求除LCA int lca(int x,int y){ if(de[x]<de[y]) swap(x,y); //移到同一高度 for(int i=19;i>=0;i--){ if(de[f[x][i]]>=de[y]) x=f[x][i]; if(x==y) return x; } for(int i=19;i>=0;i--){ if(f[x][i]!=f[y][i]) { x=f[x][i];y=f[y][i]; //一起上移 } } return f[x][0]; } int dist(int i,int j){ return de[i]+de[j]-2*de[lca(i,j)]; } int main(){ scanf("%d",&n); int x,y; for(int i=0;i<n-1;i++){ scanf("%d %d",&x,&y); adde(x,y); } inti(1,0); scanf("%d",&q); for(int i=0;i<q;i++){ scanf("%d %d",&x,&y); printf("%d ",dist(x,y)); } return 0; }
1553:【例 2】暗的连锁
树上差分的思想
还不是很懂 ,这两篇题解很好
https://www.cnblogs.com/ZhengkunJia/p/12275722.html
https://blog.csdn.net/a_pathfinder/article/details/101034619 讲了边差分、点差分
本题就是就是对边的差分,首先题目给的是一颗树,然后给我们m条附加边(非树边),在每一条非树边(x,y)添加到树边中,就会在树上(x,y)路径形成一个环。
因而我们每次读入一条附加边,就称就给x到y的路径上的所有主要边记录上“被覆盖一次”,这样再去遍历所有主要边。这样题目就变成了**给定一张无向图和一颗生成树,求每条“树边”被“非树边”覆盖了多少次。
对于我们想要切割的一条主要边,有以下3种情况:
若这条边被覆盖0次,则可以任意再切断一条附加边
若这条边被覆盖1次,那么只能再切断唯一的一条附加边
若这条边被覆盖2次及以上,没有可行的方案
所以我们求的被覆盖次树就是上述树上差分求的cf[]。
/*我们给树上每个点一个初始为0的权值,然后对每条非树边(x,y),令节点x,y的权值+1,节点lca(x,y)的权值-=2,最后深度优先遍历,求出f[x]表示以x为根节点的子树中各个点的权值和。f[x]就是x与父节点之间被“树边”覆盖的次数,时间复杂度为O(N+M)。
PS:本题对应的是树上边差分,我们最后dif[lca(u,v)]所得是lca(u,v)和pre[lca(u,v)][0]两点所在的边;另一种差分是点差分,我们就不能用dif[lca(u,v)]-=2,而是dif[lca(u,v)]–,dif[lca(pre[lca(u,v)][0])]–,因为lca(u,v)也在u…v这条路径上,它同样需要被加x。回溯的时候会从u和v两个方向都给lca(u,v)加一个x,而它只能加一个,因此dif[lca(u,v)]-=x。而lca(u,v)的爸爸则根本无法被加,在lca(u,v)已经只加一个x了,因此dif[pre[lca(u,v)]]-=x就能让lca(u,v)的爸爸不加x
树上差分的思想:
- 树上差分主要用于求解一些树上的路径问题
- 它通过利用树的一些性质,用一个差分数组来实现对一条路径的操作,这涉及到路径的 起,终点 与lca。
- 一般情况下:一个点的真实权值为其所在子树内所有点的差分数组的值的和
- 树上差分一般不适用于询问和操作嵌套的题目,这时一般用树链剖分解
https://www.cnblogs.com/cjoierljl/p/8728215.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int maxm=2e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //树上差分的思想 //还不是很懂 ,这两篇题解很好 //https://www.cnblogs.com/ZhengkunJia/p/12275722.html //https://blog.csdn.net/a_pathfinder/article/details/101034619 int n,m; int head[maxm*2],to[maxm*2],nex[maxm*2],de[maxn],tot; int lg[maxn],f[maxn][22]; //倍增数组 int dif[maxn],sta[maxn]; //差分、累计差分 int vis[maxn]; void add(int x,int y){ to[++tot]=y; nex[tot]=head[x]; head[x]=tot; } void lca_dfs(int u,int fa){ de[u]=de[fa]+1; f[u][0]=fa; for(int i=1;(1<<i)<=de[u];i++){ f[u][i]=f[f[u][i-1]][i-1]; } for(int i=head[u];i;i=nex[i]){ int v=to[i]; if(v!=fa) lca_dfs(v,u); } } int lca(int x,int y){ if(de[x]<de[y]) swap(x,y); while(de[x]>de[y]){ x=f[x][lg[de[x]-de[y]]-1]; } if(x==y) return x; for(int i=lg[de[x]]-1;i>=0;i--){ if(f[x][i]!=f[y][i]){ //别忘了这个条件 x=f[x][i]; y=f[y][i]; } } return f[x][0]; } int dfs(int f){ //求出累计差分数组 sta[f]=dif[f]; vis[f]=1; for(int i=head[f];i;i=nex[i]){ int v=to[i]; if(vis[v]) continue; sta[f]+=dfs(v); } return sta[f]; } int main(){ scanf("%d %d",&n,&m); int x,y; for(int i=0;i<n-1;i++){ scanf("%d %d",&x,&y); add(x,y); add(y,x); } for(int i=1;i<=n;i++){ lg[i]=lg[i-1]+(1<<lg[i-1]==i); //这种写法 //lg[i]=lg[i>>1]+1; } lca_dfs(1,0); for(int i=0;i<m;i++){ scanf("%d %d",&x,&y); dif[x]++; dif[y]++; dif[lca(x,y)]-=2; } dfs(1); int ans=0; for(int i=1;i<=n;i++){ if(sta[i]==0&&i!=1) ans+=m; //why i!=1 if(sta[i]==1) ans++; } printf("%d ",ans); return 0; }
1554:【例 3】异象石
这道题也有点难,需要写函数进行增加、删除、计算异象石
在线查询,会有新的、会有原来的不见了 倍增法支持在线查询
查询连接的边数 使当前所有异象石所在的点连通所需的边集的总长度最小是多少。
https://www.cnblogs.com/ljy-endl/p/11348887.html
根据题意,这张地图显然构成一棵树。我们可以先对这棵树进行深度优先遍历,求出每个点的时间戳。
通过仔细思考 瞎猜找规律不难发现,如果我们按照时间戳从小到大的顺序,把出现异象石的结点排成一圈(首尾相连哟),并且累加相邻两个结点之间的路径长度,最后得到的结果恰好就是所求结果的两倍。(不太明白的话可以在纸上画一画哟)
两个节点之间的路径长度:LCA
因此,我们可以用一个数据结构(这里我选择的是比较方便好用的C++ STL Set),按照时间戳递增的顺序,维护出现异象石的结点序列,并用一个变量ans来
记录序列中相邻两个节点之间的路径长度之和(注意序列首位也看作是相邻的,此处要特殊处理)。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //在线查询,会有新的、会有原来的不见了 倍增法支持在线查询 //查询连接的边数 使当前所有异象石所在的点连通所需的边集的总长度最小是多少。 /* https://www.cnblogs.com/ljy-endl/p/11348887.html 根据题意,这张地图显然构成一棵树。我们可以先对这棵树进行深度优先遍历,求出每个点的时间戳。 通过仔细思考 瞎猜找规律不难发现,如果我们按照时间戳从小到大的顺序,把出现异象石的结点排成一圈(首尾相连哟),并且累加相邻两个结点之间的路径长度, 最后得到的结果恰好就是所求结果的两倍。(不太明白的话可以在纸上画一画哟)然鹅我也母鸡该如何证明...要是有小可爱能帮忙证下就好惹 因此,我们可以用一个数据结构(这里我选择的是比较方便好用的C++ STL Set),按照时间戳递增的顺序,维护出现异象石的结点序列,并用一个变量ans来 记录序列中相邻两个节点之间的路径长度之和(注意序列首位也看作是相邻的,此处要特殊处理)。 */ //倍增法:求路径长度 int n,m; LL head[maxn*2],to[maxn*2],nex[maxn*2],diss[maxn*2]; LL f[maxn][21]; LL di[maxn],vis[maxn],id[maxn],xu[maxn],dep[maxn]; //分别是到根节点的距离,有没有异象石,节点i的dfs序,dfs序为i的节点 ,节点i的深度 int tot,num; set<int> ss; //用来存储异象石的结构 LL ans=0; void adde(int u,int v,int z){ to[++tot]=v; nex[tot]=head[u]; diss[tot]=z; head[u]=tot; } int lca(int x,int y){ //用来求两点间距离的 if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--){ if(dep[f[x][i]]>=dep[y]) x=f[x][i]; if(x==y) return x; } for(int i=20;i>=0;i--){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } void dfs(int x,int fa){ id[++num]=x; //记录DFS序 xu[x]=num; f[x][0]=fa; dep[x]=dep[fa]+1; //深度记录 for(int i=head[x];i;i=nex[i]){ if(to[i]!=fa){ di[to[i]]=di[x]+diss[i]; dfs(to[i],x); } } } void pre(){ //求出倍增数组, dfs(1,0); for(int j=1;j<=19;j++){ for(int i=1;i<=n;i++){ f[i][j]=f[f[i][j-1]][j-1]; } } ss.insert(-INF); ss.insert(INF); return; } LL dist(int x,int y){ return di[x]+di[y]-2*di[lca(x,y)]; } void ad(int x){ if(vis[xu[x]]) return; //如果已经存在异象石了 ss.insert(xu[x]); vis[xu[x]]=1; set<int>::iterator it=ss.upper_bound(xu[x]); int p2=(*it),p1=(*(--(--it))); if(p1!=-INF) ans+=dist(id[p1],x); if(p2!=INF) ans+=dist(x,id[p2]); if(p1!=-INF&&p2!=INF) ans-=dist(id[p1],id[p2]); // cout<<ans<<endl; } void de(int x){ if(!vis[xu[x]]) return; //这里没有异象石 ss.erase(xu[x]); vis[xu[x]]=0; set<int>::iterator it=ss.upper_bound(xu[x]); int p2=(*it),p1=(*(--it)); if(p1!=-INF) ans-=dist(id[p1],x); if(p2!=INF) ans-=dist(x,id[p2]); if(p1!=-INF&&p2!=INF) ans+=dist(id[p1],id[p2]); //cout<<ans<<endl; } LL getres(){ LL dd=(ss.size()>3)?(dist(id[*(ss.upper_bound(-INF))],id[*(--ss.lower_bound(INF))])):0; //只要实际的元素大于1,就会算dd,计算首尾的 //cout<<"dd --- "<<dd<<endl; return (dd+ans)>>1; } int main(){ scanf("%d",&n); int x,y,z; for(int i=0;i<n-1;i++){ scanf("%d %d %d",&x,&y,&z); adde(x,y,z); adde(y,x,z); } pre(); scanf("%d",&m); char op[5]; while(m--){ scanf("%s",op+1); switch(op[1]){ case '+':{ scanf("%d",&x); ad(x); break; } case '-':{ scanf("%d",&x); de(x); break; } case '?':{ printf("%lld ",getres()); break; } } } return 0; }
1555:【例 4】次小生成树
https://blog.csdn.net/regina8023/article/details/44814899
https://blog.csdn.net/YYHS_WSF/article/details/82842355
求出最小生成树,枚举每一条非树边,用它取代掉环上最大的边最优。
那么相当于求两点到lca的最大边权,直接倍增做。
但是要注意是严格最小生成树,那么这条非树边与环上最大边相同时可能取代掉的是环上的次大边,那么倍增的时候多维护一个严格次大的边即可。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int M=3e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; /* https://blog.csdn.net/regina8023/article/details/44814899 https://blog.csdn.net/YYHS_WSF/article/details/82842355 求出最小生成树,枚举每一条非树边,用它取代掉环上最大的边最优。 那么相当于求两点到lca的最大边权,直接倍增做。 但是要注意是严格最小生成树,那么这条非树边与环上最大边相同时可能取代掉的是环上的次大边,那么倍增的时候多维护一个严格次大的边即可。 */ int n,m; struct node{ int u,v,wei; }ed[M]; queue<int> q; struct edge{ int to,next,w; }e[M*2]; struct ma{ int a,b; //分别是 最大、次大 }g[maxn][21],p; int head[maxn],dep[maxn],f[maxn][21],fa[maxn]; //后两个:深度,倍增数组 int vis[M]; bool cmp(node a,node b){ return a.wei<b.wei; } int findd(int x){ if(x==fa[x]) return x; else return fa[x]=findd(fa[x]); } LL mst=0; void kru(){ sort(ed+1,ed+1+m,cmp); int cnt=1; for(int i=1;i<=m;i++){ int fa1=findd(ed[i].u); int fa2=findd(ed[i].v); if(fa1!=fa2){ cnt++; vis[i]=1; mst+=ed[i].wei; fa[fa1]=fa2; } } } int tot; void adde(int x,int y,int z){ e[++tot].to=y; e[tot].next=head[x]; e[tot].w=z; head[x]=tot; e[++tot].to=x; e[tot].next=head[y]; e[tot].w=z; head[y]=tot; } void build(){ for(int i=1;i<=m;i++){ if(vis[i]) adde(ed[i].u,ed[i].v,ed[i].wei); //建立树边 } g[1][0].a=g[1][1].b=-INF; //最小值 f[1][0]=0; //根的爸爸 dep[1]=1; q.push(1); while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==f[x][0]) continue; //是爸爸 f[y][0]=x; dep[y]=dep[x]+1; g[y][0].a=e[i].w; //最大值的初值 q.push(y); } } } void updat(ma &x,ma y){ ///用y来更新x if(x.a>y.a) x.b=max(y.a,x.b); //b记录的是次大 ,这三行都是在更新次大 else if(x.a<y.a) x.b=max(x.a,y.b); else x.b=max(x.b,y.b); x.a=max(x.a,y.a); //最大直接更新 } void ST(){ for(int j=1;(1<<j)<=n;j++){ for(int i=1;i<=n;i++){ f[i][j]=f[f[i][j-1]][j-1]; updat(g[i][j],g[i][j-1]); updat(g[i][j],g[f[i][j-1]][j-1]); } } } void mov(int &x,int deep){ //先移动到同一层 for(int i=20;i>=0;i--){ if(dep[f[x][i]]>=deep) { updat(p,g[x][i]); x=f[x][i]; } } } void getlca(int x,int y){ p.a=p.b=-INF; if(dep[x]>dep[y]) swap(x,y); mov(y,dep[x]); //先移动到同一层 if(x==y) return; for(int i=20;i>=0;i--){ if(f[x][i]!=f[y][i]){ updat(p,g[x][i]); updat(p,g[y][i]); //找到替换的那条边严格最大边 x=f[x][i]; y=f[y][i]; } } updat(p,g[x][0]); updat(p,g[y][0]); } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d %d %d",&ed[i].u,&ed[i].v,&ed[i].wei); } for(int i=1;i<=n;i++) fa[i]=i; kru(); //先求出最小生成树的权值 build(); //用非树边去更新倍增数组,g[][].a g[][].b ST(); LL ans=(LL)1e15; //这个要设置的足够大 for(int i=1;i<=m;i++){ if(!vis[i]){ //用非树边建立 getlca(ed[i].u,ed[i].v); if(p.a==ed[i].wei){ //最大便与其相等 ans=min(ans,mst+ed[i].wei-p.b); } else ans=min(ans,mst+ed[i].wei-p.a); } } printf("%lld ",ans); return 0; }
1556:Dis
模板题,输出两点之间的距离长度,有了一个长度的属性
在dfs的过程中累加到根节点的距离wei[t]=wei[u]+ed[i].wei; ,然后输出wei[x]+wei[y]-2*wei[lca(x,y)] 就可以了
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //升级了,这次问的是变得总长度最小,不是距离了 int f[maxn][21],dep[maxn]; int head[maxn]; struct node{ int wei,next,to; }ed[maxn*2]; LL wei[maxn]; int n,m,tot; void adde(int u,int v,int z){ ed[++tot].to=v; ed[tot].next=head[u]; ed[tot].wei=z; head[u]=tot; ed[++tot].to=u; ed[tot].next=head[v]; ed[tot].wei=z; head[v]=tot; } void inti(int u,int fa){ dep[u]=dep[fa]+1; f[u][0]=fa; for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=ed[i].next){ int t=ed[i].to; if(t==fa) continue; wei[t]=wei[u]+ed[i].wei; //改变在这里 inti(t,u); } } int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--){ if(dep[y]<=dep[f[x][i]]) x=f[x][i]; if(x==y) return x; } for(int i=20;i>=0;i--){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } LL dist(int x,int y){ return wei[x]+wei[y]-2*wei[lca(x,y)]; //改变在这里 } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<n;i++){ int x,y,z; scanf("%d %d %d",&x,&y,&z); adde(x,y,z); } inti(1,0); while(m--){ int x,y; scanf("%d %d",&x,&y); printf("%lld ",dist(x,y)); } return 0; }
1557:祖孙询问
不难,稍微画个图就可以了
首先在进去判断高度的时候先标记一下哪个大,看移到同一层是不是同一个节点就可以了
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int n,m,tot; int root=0; int dep[maxn]; int f[maxn][21]; struct node{ int u,v; }ed[maxn]; int head[maxn],to[maxn],nex[maxn]; void adde(int u,int v){ to[++tot]=v; nex[tot]=head[u]; head[u]=tot; to[++tot]=u; nex[tot]=head[v]; head[v]=tot; } void inti(int u,int fa){ dep[u]=dep[fa]+1; f[u][0]=fa; for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=nex[i]){ int t=to[i]; if(t==fa) continue; inti(t,u); } } int judge(int x,int y){ bool flag=0; if(dep[x]<dep[y]){ //x在上面 swap(x,y); flag=1; } for(int i=20;i>=0;i--){ if(dep[y]<=dep[f[x][i]]){ x=f[x][i]; } if(x==y) { if(flag) return 1; //x在上面,x是祖先 else return 2; } } return 0; } int main(){ scanf("%d",&n); int x,y,num=0; for(int i=0;i<n;i++){ scanf("%d %d",&x,&y); if(y==-1) { root=x; } else { ed[++num].u=x; ed[num].v=y; adde(x,y); } } inti(root,0); /* for(int i=1;i<=num;i++){ printf("%d: %d ",ed[i].u,dep[ed[i].u]); } */ scanf("%d",&m); while(m--){ scanf("%d %d",&x,&y); printf("%d ",judge(x,y)); } return 0; }
1558:聚会
现在要计算三个点的情况
求出两两lca,其中有两个相同,答案则为另一个,画画图就可以理解
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //现在要计算三个点的情况 //求出两两lca,其中有两个相同,答案则为另一个,画画图就可以理解 int n,m; int head[maxn],to[maxn],nex[maxn]; int f[maxn][21],dep[maxn]; int lc,tot; void add(int u,int v){ to[++tot]=v; nex[tot]=head[u]; head[u]=tot; to[++tot]=u; nex[tot]=head[v]; head[v]=tot; } void inti(int u,int fa){ dep[u]=dep[fa]+1; f[u][0]=fa; for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=nex[i]){ int t=to[i]; if(t==fa) continue; inti(t,u); } } int getlca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--){ if(dep[y]<=dep[f[x][i]]){ x=f[x][i]; } if(x==y) return x; } for(int i=20;i>=0;i--){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } int pos; int dist(int x,int y){ pos=getlca(x,y); return dep[x]+dep[y]-2*dep[pos]; } int que(int a,int b,int c,int end){ return (dist(a,end)+dist(b,end)+dist(c,end)); } int main(){ scanf("%d %d",&n,&m); for(int i=0;i<n-1;i++){ int a,b; scanf("%d %d",&a,&b); add(a,b); } int a,b,c; inti(1,0); //求出两两lca,其中有两个相同,答案则为另一个,画画图就可以理解 while(m--){ scanf("%d %d %d",&a,&b,&c); int A=getlca(a,b); int B=getlca(b,c); int C=getlca(a,c); if(A==B) printf("%d %d ",C,que(a,b,c,C)); else if(B==C) printf("%d %d ",A,que(a,b,c,A)); else if(C==A) printf("%d %d ",B,que(a,b,c,B)); } return 0; }
1559:跳跳棋
这道题我实在是真的是想不出来wwwww
https://www.cnblogs.com/ljy-endl/p/11365039.html
https://www.cnblogs.com/gaojunonly1/p/10353158.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; ///这道题对我这样的智障还是太难想了一点吧 //TAT //https://www.cnblogs.com/ljy-endl/p/11365039.html //https://www.cnblogs.com/gaojunonly1/p/10353158.html /* 任意一种给定的三颗棋子状态都可以转变成左右两颗棋子到中间棋子距离相等的状态,相当于达到了一种“平衡”,而在这种平衡状态又可以通过向两边跳转变成其他状态, */ struct node{ int x,y,z; //三个位置 }b1,b2; void gett(node &b){ if(b.x>b.y) swap(b.x,b.y); if(b.y>b.z) swap(b.y,b.z); if(b.x>b.z) swap(b.x,b.z); } int f1(node &b){ //返回转化为 右两颗棋子到中间棋子距离相等的状态 需要多少步 int step=0; gett(b); while(b.x+b.z!=2*b.y){ int d1=b.y-b.x; int d2=b.z-b.y; /* 假设三个数a、b、c,b-a=t1,c-b=t2。不妨设t1<t2。我们的目标就是把a、c往中间移,最终使得t1=t2。 那么显然a往中间跳的次数就是(t1-1)/t2,之后t1,t2的大小关系就会变 而且a、b、c是不计顺序的,所以直接a+=t1*(t1-1)/t2 b+=t1*(t1-1)/2 就好了 */ if(d1<d2){ int p=d2/d1; if(d2%d1==0) p--; b.x+=d1*p; //最左边的移动 b.y+=d1*p; if(b.x>b.y) swap(b.x,b.y); step+=p; } else { int p=d1/d2; if(d1%d2==0) p--; b.y-=d2*p; //最右边的移动 b.z-=d2*p; if(b.y>b.z) swap(b.y,b.z); step+=p; } } return step; } //如果a,b,c状态所在树的树根与x,y,z状态所在树的树根不同,就说明a.b.c状态永远无法到达x,y,z状态,此时就直接输出“NO”,然后结束程序。 bool judge(node b1,node b2){ //判断最后的转化后 三个位置是不是一样的 if(b1.x==b2.x&&b1.y==b2.y&&b1.z==b2.z) return 1; return 0; } node f2(node b,int l){ //转化状态 gett(b); while(l){ int d1=b.y-b.x; int d2=b.z-b.y; if(d1<d2){ int p=d2/d1; if(d2%d1==0) p--; if(p>l) p=l; b.x+=p*d1; b.y+=p*d1; if(b.x>b.y) swap(b.x,b.y); l-=p; } else { int p=d1/d2; if(d1%d2==0) p--; if(p>l) p=l; b.y-=p*d2; b.z-=p*d2; if(b.y>b.z) swap(b.y,b.z); l-=p; } } return b; } int main(){ scanf("%d %d %d",&b1.x,&b1.y,&b1.z); scanf("%d %d %d",&b2.x,&b2.y,&b2.z); gett(b1); gett(b2); //先转化为标准状态 node bb1=b1,bb2=b2; //需要的操作数 int t1=f1(bb1); int t2=f1(bb2); //操作之后是不是一样的判断 if(!judge(bb1,bb2)){ printf("NO");return 0; } int d=abs(t1-t2); //两种状态的层次差 if(t1>t2) b1=f2(b1,d); //如果是t1需要的操作数多的话,那么就由b1转化 else b2=f2(b2,d); //否则就是b2转化 //处于同一层之后 //二分 找LCA int l=0,r=min(t1,t2),ff=0; //到LCA的的距离 while(l<=r){ int mid=(l+r)>>1; bb1=f2(b1,mid); bb2=f2(b2,mid); if(judge(bb1,bb2)){ ff=mid; //深度小的那点到最近公共祖先的距离 r=mid-1; } else l=mid+1; } printf("YES "); printf("%d ",d+2*ff); // 最后输出的答案就是两种状态的层数之差+深度小的那点到最近公共祖先的距离*2。(画图易得) return 0; }