I need to solve the following competitive programming problem from coderun.ru:
Strength of a Pyramid Top
The pyramid consists of n horizontal layers of blocks: the first layer contains n blocks, the second layer contains n−1 blocks, and so on, up to the n-th layer, which consists of a single block. Each block has a certain strength, expressed as a positive integer (determined by various factors such as material type, density, etc.).Since the first layer of the pyramid is already built, it remains unchanged. Each of the following n−1 layers is of one of two types: min or max, and the blocks on it are arranged as follows:
For a min layer: the strength of each block equals the minimum of the strengths of the two blocks beneath it.
For a max layer: the strength of each block equals the maximum of the strengths of the two blocks beneath it.
You are required to determine the strength of the block at the top of the pyramid, given the structure of the first layer and the type of each of the following n−1 layers.Input format
The first line contains a single integer (1≤n≤1,000,000) — the number of layers in the pyramid.
The second line contains n positive integers r1,r2,…,rn (1≤ri≤n) — the strengths of the blocks in the first layer.
The third line contains n−1 characters from the set {
M,m}, describing the layers. The i-th character describes the (i+1)-th layer:
M— a max layer
m— a min layerOutput format
A single integer — the strength of the block on the n-th (top) level.
Limitations: time limit is 4 seconds; space limit is 512Mb
Examples
Test 1: Input
7 3 1 4 5 6 2 1 MmmMmMOutput
5Test 2: Input
2 2 1 mOutput
1
I've written a C++ code that creates a vector of double-linked lists (v) based on the rank-ordering patterns of the elements (non-decreasing vs decreasing). The idea is that for each linked list we can safely remove either its tail or head depending on whether the current operation is min or max.
If the next level is created as the minimum of two blocks from the lower level: – remove the tails of non-decreasing linked lists (getDir=1) – remove the heads of decreasing linked lists (getDir=0).
If the next level is created as the maximum of two blocks from the lower level: – remove the heads of non-decreasing linked lists (getDir=1) – remove the tails of decreasing linked lists (getDir=0).
We should also merge adjacent linked lists if:
- both are decreasing and the head of the right list is less than or equal to the tail of the left list, or
- both are non-decreasing and the head of the right list is greater than or equal to the tail of the left list.
- the pair of adjacent linked lists have only one unique value each (e.g.: {{2,2,2}; {3,3}} becomes {2,2,2,3,3})
This way, the overall time complexity of the algorithm will be about O(nlogn), which will fit within the 4-second time limit when n is up to 1,000,000.
However, this solution doesn't fit into the time limit.
Could you please either poke at the sub-optimalities of my solution/code or suggest a better one?
Here is my code:
#include <bits/stdc++.h> using namespace std; struct Node { int data; Node* next; Node* prev; Node(int val) : data(val), next(nullptr), prev(nullptr) {} }; class LinkedList { private: Node* head; Node* tail; int length; bool dir; public: LinkedList() : head(nullptr), tail(nullptr), length(0), dir(true) {} LinkedList(const LinkedList&) = delete; LinkedList& operator=(const LinkedList&) = delete; LinkedList(LinkedList&& other) noexcept { head = other.head; tail = other.tail; length = other.length; dir = other.dir; other.head = other.tail = nullptr; other.length = 0; } LinkedList& operator=(LinkedList&& other) noexcept { if (this != &other) { clear_safe(); head = other.head; tail = other.tail; length = other.length; dir = other.dir; other.head = other.tail = nullptr; other.length = 0; } return *this; } void setDir(bool d) { dir = d; } bool getDir() const { return dir; } void append(int val) { Node* newNode = new Node(val); if (!head) { head = tail = newNode; } else { tail->next = newNode; newNode->prev = tail; tail = newNode; } length++; } void prepend(int val) { Node* newNode = new Node(val); if (!head) { head = tail = newNode; } else { newNode->next = head; head->prev = newNode; head = newNode; } length++; } void pop_head() { if (!head) return; Node* toDelete = head; head = head->next; if (head) head->prev = nullptr; else tail = nullptr; delete toDelete; length--; } void pop_tail() { if (!tail) return; Node* toDelete = tail; tail = tail->prev; if (tail) tail->next = nullptr; else head = nullptr; delete toDelete; length--; } void clear_safe() { while (head) pop_head(); } Node* getHead() const { return head; } Node* getTail() const { return tail; } int getLength() const { return length; } int getHeadData() const { return head ? head->data : -1; } int getTailData() const { return tail ? tail->data : -1; } void merge(LinkedList&& other) { if (!other.head) return; if (!head) { head = other.head; tail = other.tail; length = other.length; dir = other.dir; } else { tail->next = other.head; other.head->prev = tail; tail = other.tail; length += other.length; } other.head = other.tail = nullptr; other.length = 0; } ~LinkedList() { clear_safe(); } }; int main() { ios_base::sync_with_stdio(false); cin.tie(0); int n; cin >> n; vector<LinkedList> v; LinkedList temp; bool flag = true; int inter; cin >> inter; if (n == 1) { cout << inter << endl; return 0; } temp.append(inter); cin >> inter; if (inter < temp.getTail()->data) { flag = false; temp.setDir(false); } temp.append(inter); for (int i = 0; i < n - 2; i++) { cin >> inter; if (flag) { if (inter >= temp.getTail()->data) { temp.append(inter); } else { v.push_back(std::move(temp)); temp = LinkedList(); temp.setDir(false); flag = false; temp.append(inter); } } else { if (inter < temp.getTail()->data) { temp.append(inter); } else { v.push_back(std::move(temp)); temp = LinkedList(); temp.setDir(true); flag = true; temp.append(inter); } } } v.push_back(std::move(temp)); string s; cin >> s; for (char op : s) { if (v.size() > 1) { for (int i = 0; i < v.size() - 1; i++) { int left_tail = v[i].getTailData(); int right_head = v[i+1].getHeadData(); if (op == 'm') { if (left_tail < right_head) { v[i].append(left_tail); } else { v[i+1].prepend(right_head); } } else { if (left_tail >= right_head) { v[i].append(left_tail); } else { v[i+1].prepend(right_head); } } } } vector<LinkedList> non_empty; for (auto& run : v) { if (run.getLength() > 0) { if (op == 'M') { if (run.getDir()) { run.pop_head(); } else { run.pop_tail(); } } else { if (run.getDir()) { run.pop_tail(); } else { run.pop_head(); } } if (run.getLength() > 0) { non_empty.push_back(std::move(run)); } } } if (non_empty.empty()) { break; } v = move(non_empty); vector<LinkedList> merged; merged.push_back(std::move(v[0])); for (int i = 1; i < v.size(); i++) { LinkedList& current = v[i]; LinkedList& last = merged.back(); bool canMerge = false; if (last.getDir() == current.getDir()) { if (last.getDir()) { if (last.getTailData() <= current.getHeadData()) { canMerge = true; } } else { if (last.getTailData() >= current.getHeadData()) { canMerge = true; } } } else if (last.getHeadData() == last.getTailData()) { if (current.getDir() && last.getTailData() <= current.getHeadData()) { canMerge = true; last.setDir(true); } else if (!current.getDir() && last.getTailData() >= current.getHeadData()) { canMerge = true; last.setDir(false); } else if (current.getHeadData() == last.getTailData()) { canMerge = true; if (last.getTailData() <= current.getHeadData()) { last.setDir(true); } else { last.setDir(false); } } } if (canMerge) { last.merge(std::move(current)); } else { merged.push_back(std::move(current)); } } v = move(merged); } cout << v[0].getHeadData() << endl; return 0; } As an FYI: I've also tried to solve this problem with minimax optimization and apply alpha-beta pruning, but given that mins and maxs are not necessarily going one by another, it defeats the whole idea of using minimax optimization. Besides, since the pairs overlap (each block's affects both the left and right blocks at the upper level) the pruning is not happening, though I gave it a spin. Also, alpha-beta pruning does not guarantee a quick execution.
Additionally, I contemplated on the dynamic programming, but it would require O(n^2) complexity and hence won't work here due to the time limitation (1000000^2 is too many iterations for 4 seconds)