• 《货车运输》题解--最大生成树&倍增


    货车运输

    【题目描述】

    A国有n座城市,编号从1到n,城市之间有m条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有q辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

    【输入输出格式】

    • 输入格式

      输入文件第一行有两个用一个空格隔开的整数n,m,表示A国有n座城市和m条道路。接下来m行每行3个整数x、y、z,每两个整数之间用一个空格隔开,表示从x号城市到y号城市有一条限重为z的道路。注意:x不等于y,两座城市之间可能有多条道路。接下来一行有一个整数q,表示有q辆货车需要运货。接下来q行,每行两个整数x、y,之间用一个空格隔开,表示一辆货车需要从x城市运输货物到y城市,注意:x不等于y。

    • 输出格式

    输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

      ·输入样例:

        4 3
        1 2 4
        2 3 3
        3 1 1
        3
        1 3
        1 4
        1 3

      ·输出样例:

        3
        -1
        3

    思路:

        因为要运输最多的货物,所以在全部边里需要尽量长的边,所以要一棵最大生成树。

        因为要求两点之间的最短路,数据量很大,又是树上的两点的距离,所以运用倍增。

    盲点:

        1.如何求最大生成树?--仿照求最小生成树kruskal的算法,结合并查集。

        2.如何建最大生成树?--首先把题目中的边读取进来,用结构体保存,记录起点、终点、边权。接着求出最大生成树时,边求边建树。

        3.如何求树上两点之间的最短距离?--运用倍增,记录一个w数组w[i][20],表示i向上跳2^j步,初始化为inf,更新到他时,首先edge[fa].w,转移方程是 w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1])。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring> 
    using namespace std; 
    const int maxn=50000+100;
    const int MAXN=100000+100;
    int rd(){
        int s=0;
        char c=getchar();
        while(c>'9'||c<'0')c=getchar();
        while(c<='9'&&c>='0'){s=s*10+c-'0';c=getchar();}
        return s;
    }
    //强调一下数组的问题哈,最大生成树用的边要100000多,后面倍增从0-20,是21个位置 
    int n,m,head[maxn],tot,q,w[maxn][21],deep[maxn],fa[maxn][21],ff[maxn];
    bool v[maxn];
    
    
    //题目所给信息的图,假的图 
    struct eeee{
        int f,t,w;
    }edge1[MAXN];
    //用最大生成树建的图,真的图 
    struct EDGE{
        int next,to,w;
    }edge2[maxn];
    void add(int x,int y,int z)
    {
        edge2[++tot].next=head[x];
        edge2[tot].to=y;
        edge2[tot].w=z;
        head[x]=tot;
    }
    
    
    //并查集
    int find(int x)
    {
        if(ff[x]==x)return x;
        ff[x]=find(ff[x]); 
        return ff[x];
    }//这里并查集的递归是需要理解记忆的,必须要边搜边保存 
    void unionn(int x,int y)
    {
        ff[x]=y;
    }
    bool cmp(eeee a,eeee b)
    {
        return a.w>b.w;
    }
    //最大生成树 
    void kruskal()
    {
        sort(edge1+1,edge1+1+m,cmp);
        for(int i=1;i<=n;i++)ff[i]=i;
        for(int i=1;i<=m;i++)
        {
            int x=find(edge1[i].f),y=find(edge1[i].t);
            if(x!=y)
            {
                unionn(x,y);
                add(edge1[i].f,edge1[i].t,edge1[i].w);
                add(edge1[i].t,edge1[i].f,edge1[i].w);    
            }
        }
    }
    
    
    //介入问题去求两点路径间的最小值 
    //初始化deep、w、v数组 
    void update(int u)
    {
        v[u]=1;
        for(int i=head[u];i;i=edge2[i].next)
        {
            int to=edge2[i].to;
            if(v[to])continue;
            deep[to]=deep[u]+1;
            fa[to][0]=u;
            w[to][0]=edge2[i].w;
            update(to);
        }
    }
    //倍增初始化fa、w数组,w即j节点跳2^i个节点经历的线段中最短的 
    void initial()
    {
        for(int i=1;i<=20;i++)
        {
            for(int j=1;j<=n;j++)
            {
                fa[j][i]=fa[fa[j][i-1]][i-1];
                w[j][i]=min(w[fa[j][i-1]][i-1],w[j][i-1]);
            }
        }
    }
    //LCA 模板 求答案 
    int query(int x,int y)
    {
        if(find(x)!=find(y))return -1;
        int ans=0x3f3f3f3f;
        if(deep[x]<deep[y])swap(x,y);//x为下面的
        //让x跳至与y同一层 
        for(int i=20;i>=0;i--)
        {
            if(deep[fa[x][i]]>=deep[y]){
                ans=min(ans,w[x][i]);
                x=fa[x][i];
            }
        } 
        if(x==y)return ans;//已经找到lca 
        for(int i=20;i>=0;i--)
        {
            if(fa[x][i]!=fa[y][i])
            {
                ans=min(ans,min(w[x][i],w[y][i]));
                x=fa[x][i],y=fa[y][i];
            }
        } 
        ans=min(ans,min(w[x][0],w[y][0]));// 现在的x/y向上跳一步就是他们的爸爸啦 
        return ans;
    }
    
    
    int main() {
        n=rd(),m=rd();
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            x=rd(),y=rd(),z=rd();
            edge1[i].f=x,edge1[i].t=y,edge1[i].w=z;
        }
        kruskal();
        
        //为了防止有不连通的点,需要每个都遍历 
        for(int i=1;i<=n;i++)
        {
            if(!v[i])//这里又是一个槽点,调试半天... 这个意思是v[i]为0 
            {
                deep[i]=1;
                update(i);
                fa[i][0]=i;
                w[i][0]=0x3f3f3f3f; 
            }
        }
        
        //解决问题 
        q=rd();
        initial();
        for(int i=1;i<=q;i++)
        {
            int x=rd(),y=rd();
            int ans=query(x,y); 
            printf("%d
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    C++学习笔记十关联容器
    Ubuntu下使用GDB断点Go程序
    各种语言的数字转罗码方法的实现
    为什么 ++i和i++的效果是一样的,试了javascript ,c++ java
    罗马数字转换阿拉伯数字(Java版,考虑较为全面)
    C++学习笔记九顺序容器(二) ForFreeDom 博客园
    智立方 屁话真言108:能盛事者能成事_智立方的杨石头_新浪博客
    腾讯搜搜高管吴军离职的传闻与真相
    罗马数字_百度百科
    快速深入一门语言的几个问题 Shell909090 随笔杂记
  • 原文地址:https://www.cnblogs.com/SUMMER20020929/p/10622584.html
Copyright © 2020-2023  润新知