首先,询问中给定的x和y之间的最优路径一定只经过该图中最大生成树上的边。
对于题中询问时给的的x和y,如果他们之间连通(即两点同在一个图中;因为题中给的图可能是多个图) 那么对于连接x和y的路径:
1.如果该条路径只走最大生成树上的边(若x和y连通,在无向图便可以实现这一点),最后更新出来的所有边的min值>=最大生成树上中所有边的权值的最小值
2.若该条路径经过了非最大生成树上的边,那么最后更新出来的所有边的min值只会是某条非最大生成树上的边的权值(也就是说<=最大生成树上中所有边的权值的最小值)
比较以上两种方案,显然第一种更优,所以先找出最大生成树,然后建一个只包含最大生成树上所有的边的新图,对于给定的s和l,只需在这个新图上遍历寻找即可;但对于n<10000和0<q<30000,直接遍历寻找的O(qn)的复杂度显然无法承受。
现在我们已经实现了图转树,所以对于每次询问,不妨先找出每棵最大生成树的根节点,再运用倍增处理出该点向其最远祖先的方向跳2^i步时到达的点和已经过的所有边的权值最小值;最后对于每一次询问,我们只需用LCA的处理方法处理一下即可(具体实现细节请看下面的代码)
说到这里,让我们概括一下本题的大致思路:
1.先找出原图的最大生成树,再建一个包含最大生成树的新图;
2.对于新图(注意对于源数据给了多个图的情况下,可能有多棵最大生成树)处理出每一个点向其所在的最大生成树的公共祖先跳2^i步后到达的点和已经过的所有边的权值最小值;
3.对于每一次询问,只需LCA查询即可。
AC代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int NMAXN=10001,MMAXN=100001,INF=1310668019;
int n,m,first[NMAXN<<1],next[NMAXN<<1],father[NMAXN],num,q,s,l;
bool book[NMAXN];
struct node
{
int u,v,w;
}a[MMAXN],krus[NMAXN<<1];//a:原图 krus:存最大生成树的新图
int tot,root[NMAXN],deep[NMAXN],F[NMAXN][16],stmin[NMAXN][16];
//deep[i]:点i在最大生成树中的深度
//F[i][j]:在最大生成树中从点i跳2^j步跳到的点的编号
//stmin[i][j]:在最大生成树中从点i跳2^j步所经过的所有边的权值最小值
int find(int x)//并查集中的找爸爸函数
{
if(x==father[x]) return x;
else return father[x]=find(father[x]);
}
bool cmp(node a,node b)
{
return a.w>b.w;
}//重载sort
void dfs(int now,int fa,int xuhao)
//LCA中的预处理(在LCA的基础上多维护了个stmin数组)
{
deep[now]=deep[fa]+1;//标记该点深度
F[now][0]=fa,stmin[now][0]=krus[xuhao].w;
//显然从now跳一步到达的点是他的父亲,只经过了一条边
for(int i=1;(1<<i)<=deep[now];i++)
//deep[now]-(1<<i)必须>=0,也就是说从该点跳最远能跳到根结点
{
stmin[now][i]=min(stmin[now][i-1],stmin[F[now][i-1]][i-1]);
//倍增思想:处理从now前进2^i步经过的所有边的权值的最小值
F[now][i]=F[F[now][i-1]][i-1];
//从now前进2^i步到达的点=从now前进2^(i-1)步到达的点再前进2^(i-1)步到达的点
}
int k=first[now];//遍历now结点的出边
while(k!=0)
{
if(krus[k].v!=fa) dfs(krus[k].v,now,k);
//注意krus[k].v!=fa,否则便会往父亲找,进而死循
k=next[k];
}
}
int LCA(int a,int b)//询问s到l的最优路径权值最小值
{
if(deep[a]>deep[b]) swap(a,b);//LCA中的
int nowa=INF,nowb=INF;
//nowa:从a点跳到a与b的最近公共祖先所经过的所有边的权值的最小值
//nowb:从b点跳到a与b的最近公共祖先所经过的所有边的权值的最小值
for(int i=15;i>=0;i--)
{
if(deep[a]<=deep[b]-(1<<i))//显然不能跳到根结点以外的点
{
nowb=min(nowb,stmin[b][i]);//边走边比较
b=F[b][i];//把b更新为从原来的b跳2^i步到达的点
}
}//把s和l中深度较深的那个点提到和另外一个点深度一样的地方
if(a==b) return nowb;//a=b则说明a是b的一个祖先,直接返回nowb即可
for(int i=15;i>=0;i--)
{
if(F[a][i]==F[b][i]) continue;
//此时i为a,b的祖先,但不一定是最近公共祖先
else
{
nowa=min(nowa,stmin[a][i]),a=F[a][i];
nowb=min(nowb,stmin[b][i]),b=F[b][i];//边走边比较和更新
}
}
nowa=min(nowa,stmin[a][0]),nowb=min(nowb,stmin[b][0]);
//因为对于上面的for循环中F[a][i]=F[b][i],还有一种情况就是当前的F[a][i]就是LCA
//但我们由于对这种情况在for中不更新a和b,最终a,b更新到s和l的LCA的子结点
int ans=min(nowa,nowb);//根据nowa和nowb的定义,ans=min(nowa,nowb)
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
sort(a+1,a+m+1,cmp);
for(int i=1;i<=n;i++) father[i]=i;//对并查集中的father数组初始化
for(int i=1;i<=m;i++)//建最大生成树
{
if(num>>1==n-1) break;
int r1=find(a[i].u),r2=find(a[i].v);//判断(a[i].u和a[i].v是否在同一集合中
if(r1!=r2)
{
father[r2]=r1;
krus[++num].u=a[i].u,krus[num].v=a[i].v,krus[num].w=a[i].w;
next[num]=first[krus[num].u],first[krus[num].u]=num;
krus[++num].u=a[i].v,krus[num].v=a[i].u,krus[num].w=a[i].w;
next[num]=first[krus[num].u],first[krus[num].u]=num;
//注意在建立存最大生成树的新图时建双向边
}
}
for(int i=1;i<=n;i++) find(i);//找到每一个点的最远祖先
//找出哪些点的祖先=自己,这些点即为该点所在的最大生成树的根结点
for(int i=1;i<=n;i++)
{
if(book[father[i]]==0)
{
book[father[i]]=1;
root[++tot]=father[i];//用root数组存储每一棵最大生成树的根结点
}
}
for(int i=1;i<=tot;i++)//tot为最大生成树的根结点个数
{
int kk=first[root[i]];
while(kk!=0)
{
dfs(krus[kk].v,root[i],kk);
kk=next[kk];
}//对每一棵最大生成树进行预处理
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d",&s,&l);//s和l即为询问中的起点和终点
if(father[s]!=father[l]) printf("-1\n");
//father[s]!=father[l]即说明这两个点分属两个互不连通的图,无法从s到达l
else printf("%d\n",LCA(s,l));
//如果这两个点连通,则在求s和l的LCA时顺便找出所经过的路径最小值即可
}
return 0;
}
1166

被折叠的 条评论
为什么被折叠?



