题目理解

n个城市对应n个结点,城市之间的路径对应结点之间的边。我们删除某一条边。假设有m条两点之间的路径经过该边,该边的权值为w[i],输出为m*w[i]的最大值。

方法一

对于题意的理解,我们删除一条边,其权重为w[i];如果在原来的图中,有m条路径包含了该边,则最后输出代价为w[i]*m。那m是多少呢?

n个城市,n-1条边,意味着这是一个树的结构,图中不存在回路。因此,删除一条边会将树分为两个不连通的子树a,b,即可以把删除的边看作原来a,b之间的桥梁。a中结点到b中结点的路径必然包含了桥梁。所以说,m = a的结点个数*b的结点个数。

示意图如下(以删除3-6之间的边为例,子树a包含1、4、5、6、7,子树b包含2、3):
图片说明

我们先用邻接矩阵存储边和结点的信息。
然后遍历每条边,该边的两个结点必然属于不同的两个子树。分别以两个结点i,j为起点,对子树i进行深度优先搜索(可以保证不会搜到另一棵子树上,否则出现回路;但是需要传入j,判断第一步不能走到j),计算出子树i的结点个数。对于子树j同理。最后得到删除该边的代价,并与maxm比较。

具体代码如下:

class Solution {
public:
    vector<pair<int,int>> path[100001];
    int count = 1;
    int dfs(int start, int pre, int neighbour)
    {
        for (int i=0;i<path[start].size();i++)
        {
            //判断下一步去的结点不能是上一步来的结点,也不能是另一个树的结点
            if (path[start][i].first != pre && path[start][i].first!=neighbour)
            {
                //遍历到一个新结点,计数器就加一
                count++;
                dfs(path[start][i].first, start, neighbour);
            }
        }
        return count;
    }

    long long solve(int n, vector<int>& u, vector<int>& v, vector<int>& w) {
        int maxm = 0;
        for (int i=0;i<n-1;i++)
        {
            path[u[i]].push_back({v[i],w[i]});
            path[v[i]].push_back({u[i],w[i]});
        }
        for (int i=0;i<n-1;i++)
        {
            //每次dfs之前,count要初始化
            count = 1;
            int count1 = dfs(u[i],-1,v[i]);
            count = 1;
            int count2 = dfs(v[i],-1,u[i]);
            maxm = max(maxm, count1*count2*w[i]);
        }
        return maxm;
    }
};

时间复杂度:。对于每一条边,都对所有结点进行了一次遍历。
空间复杂度:。创建了一个邻接矩阵来记录边和结点的信息。

方法二

方法一中对于每一个不同的子树都需要通过dfs遍历,来计算结点个数,消耗的代价过大,可以进行优化。

当我们以某个结点为根的时候,可以把图转换为一棵树,于是对于每条边,我们把树分为父结点所在的树a和子节点为根的树b。以某个结点为根的子树的结点总数,等于其每个子树的结点数求和,再加1。所以我们可以通过dfs求出b的结点总数,而子树a的结点总数为n-|b|。

这样,我们可以通过dfs,在遍历到一个点的时候,以其为根的子树的结点总数是已知的了,可以计算出对应边(该点与其父结点之间的边)的破坏代价,从而降低了时间复杂度。

具体代码如下:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 城市个数
     * @param u int整型vector 
     * @param v int整型vector 
     * @param w int整型vector 
     * @return long长整型
     */
    vector<pair<int,int>> path[100001];
    int count[100001];
    long long maxm = 0;
    void dfs(int start, int pre, int n)
    {
        //初始化,该结点所在的子树中必包含自身
        count[start] = 1;
        for (int i=0;i<path[start].size();i++)
        {
            //判断下一步去的结点不能是上一步来的结点
            if (path[start][i].first != pre)
            {
                int next = path[start][i].first;
                dfs(next, start, n);
                long long t = count[next]*(n - count[next])*path[start][i].second;
                maxm = max(maxm,t);
                //将已经求得的该结点的子树的结点数加上去
                count[start] += count[next];
            }
        }
    }

    long long solve(int n, vector<int>& u, vector<int>& v, vector<int>& w) {
        for (int i=0;i<n-1;i++)
        {
            path[u[i]].push_back({v[i],w[i]});
            path[v[i]].push_back({u[i],w[i]});
        }
        dfs(1,0,n);
        return maxm;
    }
};

时间复杂度:。在遍历点的同时,判断了每条边的破坏代价,只遍历了一次。
空间复杂度:。创建了一个邻接矩阵来记录边和结点的信息。