思路:

题目的主要信息:

  • 一个N个城市,A数组中记录的是访问每个城市所需的花费,V是初始所有的总预算
  • 如果同时访问了两个城市,需要满足List中Point记录的先后顺序
  • 优先访问花费最小的城市,第二优先级是下标最小
  • 求满足预算条件下,最多访问的城市数

因为有优先级,我们用到了一个优先队列,并且重载了比较符号。

方法一:暴力解法
具体做法:
直接模拟访问过程,city数组保存城市之间的次序关系,dp数组保存每个城市的前序访问还剩下多少个,flag记录该城市有无被访问过,初始时没有前序城市的城市先入队列。我们可以遍历队列,每次弹出都是花费最小或者下标最小的城市,我们访问该城市,然后将其相邻的后续城市的前序数量减一,最后我们遍历每个城市,如果该城市的前序为0且没有被访问过,则加入优先队列中,等待访问。
这其中使用到了拓扑排序的思想。

class Solution {
public:
     struct node{
        int val;
        int index;
        bool friend operator <(node a, node b) { //花费优先,然后时下标
            if(a.val == b.val) 
                return a.index > b.index;  
            return a.val > b.val;
        }
    };
    int Travel(int N, int V, vector<int>& A, vector<Point>& list) {
        vector<int> dp(N + 1, 0); //记录有多少个前序城市
        int res = 0;
        vector<vector<int> > city(N + 1, vector<int> ()); //记录城市之间的次序
        for(int i = 0; i < list.size(); i++){
            city[list[i].x].push_back(list[i].y);
            dp[list[i].y]++; 
        }
        priority_queue<node> q;
        vector<bool> flag(N + 1, false);
        for(int i = 1; i <= N; i++){
            if(dp[i] == 0){
                q.push({A[i - 1], i});  //没有前序的城市可以直接放入队列,优先访问花费最小的
                flag[i] = true; //记录访问过该点
            }
        }
        while(!q.empty()){
            node temp = q.top();
            q.pop();
            if(temp.val > V)  //预算不够了
                break;
            V -= temp.val;
            res++;
            for(int i = 0; i < city[temp.index].size(); i++) //该城市的所有后序城市的前序数量减一
                dp[city[temp.index][i]]--;
            for(int i = 1; i <= N; i++){ //遍历每个城市,如果该城市的前序为0且没有被访问过
                if(dp[i] == 0 && !flag[i]){
                    q.push({A[i - 1], i});
                    flag[i] = true;
                }
            }
        }
        return res;
    }
};

复杂度分析:

  • 时间复杂度:,优先队列每次存入是,最坏嵌套遍历两次数组
  • 空间复杂度:,优先队列、辅助数组dp、flag都是,但是记录城市次序的数组最坏可能达到

方法二:优化
具体做法:
方法一,我们使用了flag数组记录城市有无被访问过,这其实是旅行Ⅱ 的遗留问题,在这题中一旦我们使用了优先队列,那么每次dp数组中的元素归零时,它一定还没有被访问过,而只可能收到这个前序城市的影响,因此我们可以将其直接加入队列中,不需要使用flag数组。如图所示,图中以小顶堆代替优先队列:
图片说明
从后面的分析中,我们可以看到虽然最坏的时间复杂度没有变化,但是整体性能有多提高,平局时间和空间都有减少。

class Solution {
public:
     struct node{
        int val;
        int index;
        bool friend operator <(node a, node b) {//花费优先,然后时下标
            if(a.val == b.val) 
                return a.index > b.index;
            return a.val > b.val;
        }
    };
    int Travel(int N, int V, vector<int>& A, vector<Point>& list) {
        vector<int> dp(N + 1, 0); //记录有多少个前序城市
        int res = 0;
        vector<vector<int> > city(N + 1, vector<int> ()); //记录城市之间的次序
        for(int i = 0; i < list.size(); i++){
            city[list[i].x].push_back(list[i].y);
            dp[list[i].y]++;
        }
        priority_queue<node> q;
        for(int i = 1; i <= N; i++)
            if(dp[i] == 0)
                q.push({A[i - 1], i});
        while(!q.empty()){
            node temp = q.top();
            q.pop();
            if(temp.val > V)  //预算不够了
                break;
            V -= temp.val;
            res++;
            for(int i = 0; i < city[temp.index].size(); i++){ //该城市的所有后序城市的前序数量减一
                dp[city[temp.index][i]]--;  
                if(dp[city[temp.index][i]] == 0)  //刚刚为0的城市一定还没有被访问过
                    q.push({A[city[temp.index][i] - 1], city[temp.index][i]});
            }
        }
        return res;
    }
};

复杂度分析:

  • 时间复杂度:,优先队列每次存入是,最坏情况是一个城市之间的次序是一个无向完全图,city数组第二维平均大小为
  • 空间复杂度:,为最坏时city数组的大小