此题写法蛮多的,倍增和启发式合并的写法想必大家已经了解了,本人稍微提一下好了。
而至于按秩合并和整体二分的写法,本人就稍微详细地讲解一下。
解法一:生成树+倍增
用克鲁斯卡尔算法构建出若干棵最大生成树,那么每个询问就转化成了求x到y路径上的最小边权,用倍增维护即可。
代码:
#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
bool f;
int n,m,q;
struct node {
int st,ed,v;
} Edge[MAXN];
int pre[MAXN];
bool cmp(node A,node B) {
return A.v>B.v;
}
int Find(int x){
if(pre[x]==x)return x;
return pre[x]=Find(pre[x]);
}
struct ______________{
vector<node>G[MAXN];
int F[MAXN][20],num[MAXN][20],lg[MAXN],deep[MAXN],root[MAXN];
bool mark[MAXN],vis[MAXN];
void DFS(int x,int fa,int val,int rt){
root[x]=rt;
F[x][0]=fa;
num[x][0]=val;
deep[x]=deep[fa]+1;
for(int i=1;(1<<i)<=deep[x];i++)F[x][i]=F[F[x][i-1]][i-1],num[x][i]=min(num[x][i-1],num[F[x][i-1]][i-1]);
for(int i=0;i<G[x].size();i++){
int t=G[x][i].ed,val=G[x][i].v;
if(t==fa)continue;
DFS(t,x,val,rt);
}
}
int LCA(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int res=2e9+7;
while(deep[x]>deep[y]){
res=min(res,num[x][lg[deep[x]-deep[y]]-1]);
x=F[x][lg[deep[x]-deep[y]]-1];
}
if(x==y)return res;
for(int i=lg[deep[x]]-1;i>=0;i--){
if(F[x][i]==F[y][i])continue;
res=min(res,num[x][i]);
res=min(res,num[y][i]);
x=F[x][i],y=F[y][i];
}
res=min(res,num[x][0]);
res=min(res,num[y][0]);
return res;
}
void work(){
for(int i=1;i<=MAXN-10;i++)lg[i]=lg[i-1]+((1<<lg[i-1])==i);
for(int i=1;i<=n;i++)pre[i]=i;
for(int i=1; i<=m; i++) {
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
Edge[i]=node {x,y,z};
}
sort(Edge+1,Edge+1+m,cmp);
for(int i=1;i<=m;i++){
int fx=Find(Edge[i].st),fy=Find(Edge[i].ed);
if(fx==fy)continue;
mark[Edge[i].st]=mark[Edge[i].ed]=true;
pre[fx]=fy;
G[Edge[i].st].push_back(node{0,Edge[i].ed,Edge[i].v});
G[Edge[i].ed].push_back(node{0,Edge[i].st,Edge[i].v});
}
for(int i=1;i<=n;i++){
if(!root[i]){
DFS(i,0,0,i);
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int x,y;
scanf("%d %d",&x,&y);
if(root[x]!=root[y]){
puts("-1");
continue;
}
printf("%d
",LCA(x,y));
}
}
}p100;
bool ff;
int main() {
scanf("%d %d",&n,&m);
p100.work();
return 0;
}
解法二:并查集+启发式合并
先把询问挂在点上,并查集合并时不路径压缩,把询问数量小的点向大的点合并,同时更新询问答案。
代码:
#include<bits/stdc++.h>#define MAXN 100010using namespace std;
int pre[MAXN],q,n,m,ans[MAXN];
struct node{
int st,ed,v;
}Edge[MAXN];
struct Query{
int ed,id;
};
vector<Query> Q[MAXN];
bool cmpEdge(node A,node B){
return A.v>B.v;
}
int Find(int x){
while(1){
if(pre[x]==x)return x;
x=pre[x];
}
}
void merge(int x,int y,int val){
int fx=Find(x),fy=Find(y);
if(fx==fy)return;
if(Q[fx].size()>Q[fy].size())swap(fx,fy);
pre[fx]=fy;
for(int i=0;i<Q[fx].size();i++){
int t=Q[fx][i].ed,id=Q[fx][i].id;
if(ans[id]!=-1)continue;
int ft=Find(t);
if(ft==fy)ans[id]=val;
}
for(int i=0;i<Q[fx].size();i++)Q[fy].push_back(Q[fx][i]);
}
int main() {
memset(ans,-1,sizeof(ans));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int st,ed,v;
scanf("%d %d %d",&st,&ed,&v);
Edge[i].st=st,Edge[i].ed=ed,Edge[i].v=v;
}
sort(Edge+1,Edge+1+m,cmpEdge);
scanf("%d",&q);
for(int i=1;i<=q;i++){
int x,y;
scanf("%d %d",&x,&y);
Q[x].push_back((Query){y,i});
Q[y].push_back((Query){x,i});
}
for(int i=1;i<=n;i++)pre[i]=i;
for(int i=1;i<=m;i++)merge(Edge[i].st,Edge[i].ed,Edge[i].v);
for(int i=1;i<=q;i++)printf("%d
",ans[i]);
return 0;
}
解法三:整体二分+并查集
假设只有一个询问,那么我们肯定是二分载重量,然后连接大于等于载重量的边,判断x,y是否连通。
那么对于很多询问呢?
考虑到如果这些询问的mid值是具有单调性的,那么当前连边情况就可以由上一个直接转移过来(不需要重新加边)。
因此,我们可以先将mid值从大到小排序,然后再查过来,同时不断加边。
代码:
#include<bits/stdc++.h>#define MAXN 100010using namespace std;
int pre[MAXN],q,n,m,Ans[MAXN];
struct node{
int st,ed,v;
}Edge[MAXN];
bool cmpEdge(node A,node B){
return A.v>B.v;
}
struct Query{
int l,r,mid,ans,id,st,ed;
}Q[MAXN];
bool cmpQuery(Query A,Query B){
return A.mid>B.mid;
}
int Find(int x){
if(pre[x]==x)return x;
return pre[x]=Find(pre[x]);
}
void merge(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx==fy)return;
pre[fx]=fy;
}
bool check(int x,int y){
int fx=Find(x),fy=Find(y);
return (fx==fy);
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int st,ed,v;
scanf("%d %d %d",&st,&ed,&v);
Edge[i].st=st,Edge[i].ed=ed,Edge[i].v=v;
}
sort(Edge+1,Edge+1+m,cmpEdge);
scanf("%d",&q);
for(int i=1;i<=q;i++){
int x,y;
scanf("%d %d",&x,&y);
Q[i]=(Query){1,100000,50000,-1,i,x,y};
}
int cnt=20;
while(cnt--){
for(int i=1;i<=n;i++)pre[i]=i;
for(int i=1;i<=q;i++)Q[i].mid=(Q[i].l+Q[i].r)>>1;
sort(Q+1,Q+1+q,cmpQuery);
for(int i=1,j=1;i<=q;i++){
if(Q[i].l>Q[i].r)continue;
while(Edge[j].v>=Q[i].mid&&j<=m){
merge(Edge[j].st,Edge[j].ed);
j++;
}
if(check(Q[i].st,Q[i].ed)){
Q[i].ans=Q[i].mid;
Q[i].l=Q[i].mid+1;
}
else Q[i].r=Q[i].mid-1;
}
}
for(int i=1;i<=q;i++)Ans[Q[i].id]=Q[i].ans;
for(int i=1;i<=q;i++)printf("%d
",Ans[i]);
return 0;
}
解法四:并查集+按秩合并
跟第一种写法差不多,只不过构建最大生成树时我们不路径压缩,用按秩合并的方法,把深度小的点合并到大的点上面。
注意了,此时连边并不是把两个点直接连边,而是把它们的祖先连边(边权还是一样)。
可以证明:因为我们是按边从大到小加的,而要查询路径上的边权最小值。此时,这条边把两个集合合并在了一起,那么一个集合到另一个集合必定要经过这条边,而这条边又是目前最小的(其它边可以不管了),所以我们只用确保路径经过了这条边即可。
按照这样构成的生成树深度最多只有log(n),因此找路径暴力遍历即可。
代码:
#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,m,q;
struct node {
int st,ed,v;
} Edge[MAXN];
int pre[MAXN],dis[MAXN],deep[MAXN],W[MAXN],root[MAXN];
vector<node> G[MAXN];
bool cmp(node A,node B) {
return A.v>B.v;
}
int Find(int x) {
while(1){
if(pre[x]==x)return x;
x=pre[x];
}
}
void merge(int x,int y,int v){
int fx=Find(x),fy=Find(y);
if(fx==fy)return;
if(dis[fx]>dis[fy])swap(fx,fy);
pre[fx]=fy;
if(dis[fx]==dis[fy])dis[fy]++;
G[fx].push_back((node){0,fy,v});
G[fy].push_back((node){0,fx,v});
}
void DFS(int x,int fa,int val,int k){
root[x]=k;
deep[x]=deep[fa]+1;
pre[x]=fa;
W[x]=val;
for(int i=0;i<G[x].size();i++){
int t=G[x][i].ed;
if(t==fa)continue;
DFS(t,x,G[x][i].v,k);
}
}
int LCA(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int res=2e9+7;
while(deep[x]>deep[y])res=min(res,W[x]),x=pre[x];
while(x!=y){
res=min(res,W[x]),res=min(res,W[y]);
x=pre[x],y=pre[y];
}
return res;
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1; i<=n; i++)pre[i]=i;
for(int i=1; i<=m; i++) {
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
Edge[i]=(node) {x,y,z};
}
sort(Edge+1,Edge+1+m,cmp);
for(int i=1;i<=m;i++){
merge(Edge[i].st,Edge[i].ed,Edge[i].v);
}
for(int i=1;i<=n;i++){
if(!root[i])DFS(i,0,0,i);
}
scanf("%d",&q);
for(int i=1; i<=q; i++) {
int x,y;
scanf("%d %d",&x,&y);
if(root[x]!=root[y]) {
puts("-1");
continue;
}
printf("%d
",LCA(x,y));
}
return 0;
}