P1967 货车运输

点击打开链接

首先,询问中给定的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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值