緒論
最近發現Leetcode每周的周賽難度挺適合我的,而且時間也比較友好(不像Codeforces每次都是半夜)。所以連續參加了三周的周賽。這次才想起來應該記錄一下自己的參賽歷程。一方面是總結經驗,另一方面有了記錄就更有動力去提升,去參加下一次比賽。
題目分析
題目鏈接:https://leetcode-cn.com/contest/weekly-contest-284/
還是往常一樣四道題,難度依次提升。
A:找出數組中的所有 K 近鄰下標
簡單模擬,對于每一個key
,其附近的2k+1個元素都是合法的,需要注意處理重復。為此我們用一個last
變量進行記錄上一次記錄的最后一個元素的位置。
namespace A {class Solution {public:vector<int> findKDistantIndices(vector<int>& nums, int key, int k) {vector<int> ret;int n = nums.size();int last = -1;auto add = [&](int l, int r) {r = min(r, n - 1);l = max(l, last + 1);for (; l <= r; ++l) ret.push_back(l);last = r;};for (int i = 0; i < n; ++i) {if (nums[i] == key) {add(i - k, i + k);}}return ret;}};
}
B:統計可以提取的工件
也是簡單模擬,因為數據量比較小,所以無腦做就可以。如果數據量比較大可能是一道比較有意思的題。
namespace B {class Solution {public:int digArtifacts(int n, vector<vector<int>>& artifacts, vector<vector<int>>& dig) {vector<vector<bool>> vis(n, vector<bool>(n));for (auto &pr : dig) {vis[pr[0]][pr[1]] = true;}int x0, x1, y0, y1;int ans = 0;auto check = [&](auto &&tool) -> bool {x0 = tool[0]; y0 = tool[1]; x1 = tool[2]; y1 = tool[3];for (int i = x0; i <= x1; ++i) {for (int j = y0; j <= y1; ++j) {if (!vis[i][j]) return false;}}return true;};for (auto &tool : artifacts) {if (check(tool)) ++ans;}return ans;}};
}
C:K 次操作后最大化頂端元素
這個題如果還是想要通過模擬去做那么就會毫無頭緒。因為觀察到最后要求的是棧頂的元素最大。而我們如何進行這k次操作的情況是非常多的,我們應該觀察哪些元素可以通過k次操作放到棧頂。
假如對棧中的元素從1開始編號。如果我們直接出棧k次,那么是可以得到第k+1個元素的,這個也是我們能夠看到的最后一個值。
對1到k-1個元素,我們都可以通過將他們的在最后一步入棧從而讓他們放到棧頂。
需要進行討論的就是我們能夠讓第k個元素放到棧頂?答案是不行(可以通過簡單模擬驗證一下)。下面我們簡單證明一下。
為了讓第k個元素放到棧頂,我們必須彈出前k-1個操作,這樣我們就只能再操作一下,而一下無論是彈出還是插入都不能讓第k個元素放到棧頂。
經過上面的討論,我們發現,其實題目的意思就是讓我們去找前k+1個元素除去第k個元素的最大值。
但是還需要注意的一點是當n為1的情況(有時間我再證明一下,當時沒怎么想清楚在這里還wa了一發)
namespace C {/** 前k - 1的最大值肯定可以取到* 第k個元素取不到* 第k + 1個元素可以取到*/class Solution {public:int maximumTop(vector<int>& nums, int k) {int n = nums.size();if (n == 1 && (k % 2 == 1)) return -1;int ans = -1;int kk = min(k - 1, n);for (int i = 0; i < kk; ++i) {ans = max(ans, nums[i]);}if (k < n) {ans = max(ans, nums[k]);}return ans;}};
}
D:得到要求路徑的最小帶權子圖
這個題差一點點就做出來了,思路是正確的,但是編碼有一個小失誤(忘記了優先隊列元素的含義)
剛開始的思路是求src1和src2到dest的最短路的和,如果兩個的最短路有重復就減去重復的。后來發現的例外:主要是因為減去的那個重復不一定是最大的,存在不是最短路的兩個路徑但是重復的成分很高,整體的和反而更小。
后來再思考了一下,那我不知道那個是重復路徑的起點,不如我遍歷一下,讓每一個都當做起點。這樣子的路徑和就是dis[src1][k]+dis[src2][k]+dis[k][dest]
想到這里我覺得我找到了正確的思路,想要用一下Floyed去求最短路。一看節點個數1e5,然后就意識到肯定會超時。因為Floyed的復雜度是O(n^3)
那么只能進行優化,考慮正向建圖求src1和src2到每個節點的距離,再反向建圖求每個節點到dest的距離。因為數據量很大,所以要求不能使用簡單的Dijkstra,要使用最小堆優化的Dijkstra,這樣每次Djikstra的復雜度是O(nlogn),最后遍歷一下的復雜度是O(n)。
但是在寫Dijkstra的時候我是抄的板子,沒有去理解優先隊列節點的含義,最終導致出錯了,也算是咎由自取吧。
吃了個飯回來又過了,淚目。
namespace D {/** 假如src1在src2到dest的路徑上或者返過來,則是平凡的情況*/class Solution {using ll = long long;struct HeapNode {ll d;int u;HeapNode(ll d_, int u_):d(d_), u(u_){}bool operator <(const HeapNode&rhs) const {return d > rhs.d;}};public:long long minimumWeight(int n, vector<vector<int>>& edges, int src1, int src2, int dest) {vector<vector<pair<int, int>>> graph(n), reverse_graph(n);//建圖int u, v, w;for (auto &edge : edges) {u = edge[0]; v = edge[1]; w = edge[2];graph[u].emplace_back(v, w);reverse_graph[v].emplace_back(u, w);}vector<ll> dis1(n), dis2(n), dis3(n);vector<bool> vis(n);auto dijkstra = [n, &vis](auto &&graph, auto &&dis, int s) {priority_queue<HeapNode> q;for (int i = 0; i < n; ++i) {dis[i] = LONG_LONG_MAX;}dis[s] = 0;for (int i = 0; i < n; ++i) vis[i] = false;q.emplace(0, s);while (!q.empty()) {HeapNode x = q.top(); q.pop();int u = x.u;if (vis[u]) continue;vis[u] = true;//print(u);auto &edges = graph[u];for (auto &pr : edges) {int v = pr.first;int w = pr.second;//print("\t", u ,v ,w);if (dis[v] > dis[u] + w) {dis[v] = dis[u] + w;q.emplace(dis[v], v);}}}};dijkstra(graph, dis1, src1);dijkstra(graph, dis2, src2);dijkstra(reverse_graph, dis3, dest);ll ans = LONG_LONG_MAX;for (int k = 0; k < n; ++k) {if (dis1[k] == LONG_LONG_MAX || dis2[k] == LONG_LONG_MAX || dis3[k] == LONG_LONG_MAX) {continue;}ans = min(ans, dis1[k] + dis2[k] + dis3[k]);}if (ans == LONG_LONG_MAX) return -1;else return ans;}};
}
經驗總結
- 重復的邊對Dijkstra算法是沒有影響的
- Dijkstra算法需要的是最小堆,而默認的小于號得到的是最大堆,因此應該在重載小于號的時候讓含義反過來。之所以會出現這樣子是和堆的實際生成有關系(上游下游什么的,已經忘記了,有時間復習一下)
- 為了避免初始化為正無窮的時候兩個正無窮相加溢出,我們可以在相加前進行判斷。
- 需要注意Dijkstra對節點標記為已處理是在彈出堆頂元素進行的,而不是在入堆的時候進行的。這一點和一般的BFS不同。Dijkstra算法正確性主要是距離源節點最近的節點的最短路徑已經確認。
- 可以放心大膽地用
emplace
,雖然不知道為什么有一次在emplace
的時候報錯了,當時心態很崩。