Skip to content
This repository was archived by the owner on Jun 26, 2025. It is now read-only.

Commit 78d6d8c

Browse files
authored
Merge pull request #2 from ProgSoc/notminhkhanh/time-traveler-trading-company
Merge Time-Traveler Trading Question from Minh Khanh
2 parents fd8faae + b2165ad commit 78d6d8c

File tree

3 files changed

+258
-0
lines changed

3 files changed

+258
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.vscode/

competition/time-traveler/prob.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---toml
2+
[fuzz]
3+
exec = ["g++", "--std=c++20", "src/main.cpp", "-o", "time-traveler", "&&", "./time-traveler", "generate"]
4+
env = {}
5+
6+
[judge]
7+
exec = ["./time-traveler", "verify"]
8+
9+
[problem]
10+
points = 15
11+
difficulty = 2
12+
---
13+
14+
# ⏰ Time Traveler's Trading Company
15+
16+
Congratulations, you've built a working time machine. You have so many ideas in changing the past for the betterment of our world. But messing with the past can be dangerous, especially for those you care about. So instead of altering major events, you decide to do something subtle: hacking the stock market.
17+
18+
You have gathered the stock prices for `N` days and need to calculate the maximum profit you can achieve without jeopardizing the space-time continuum.
19+
20+
To avoid raising suspicions from ASIC or impacting the market significantly, you set strict rules for yourself:
21+
- You start with an unlimited amount of capital borrowed from the bank, and own `0` share at the start of day `1`
22+
- You can only sell a share when you own at least `1` of them
23+
- Each day you will either buy one share, sell one share, or do nothing (at most 1 action per day)
24+
- At the end of day `N`, your position needs to be `0` to avoid risk exposure
25+
26+
## Input
27+
28+
- The first line contains `T` - the number of testcases
29+
- For the next `T` pairs of lines (each representing a seperate testcase):
30+
- The first line contains an integer `N` - the number of days
31+
- The second line contains `N` integers `price[1], price[2], ..., price[n]` - the stock prices for the next `N` days, seperated by space.
32+
33+
## Output
34+
35+
Output `T` lines of number, each contains a single integer representing the maximum profit you can make for each test case.
36+
37+
## Constraints
38+
39+
- `2 <= N <= 3 * 10^5`
40+
- `1 <= price[i] <= 10^6`
41+
- The sum of `N` over all test cases will not exceed `3*10^5`
42+
43+
## Example
44+
45+
#### Input
46+
47+
```
48+
1
49+
9
50+
10 5 4 7 9 12 6 2 10
51+
```
52+
53+
#### Output
54+
55+
```
56+
20
57+
```
58+
59+
#### Explanation
60+
61+
To get the maximum profit, we will make these transactions:
62+
- Buy a share at day `2` at price `5`
63+
- Buy a share at day `3` at price `4`
64+
- Sell a share at day `5` at price `9`
65+
- Sell a share at day `6` at price `12`
66+
- Buy a share at day `8` at price `2`
67+
- Sell a share at day `9` at price `10`
68+
69+
The total profit is `0 - 5 - 4 + 9 + 12 - 2 + 10 = 20`
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#include <iostream>
2+
#include <vector>
3+
#include <queue>
4+
#include <cassert>
5+
#include <exception>
6+
#include <random>
7+
#include <algorithm>
8+
#include <fstream>
9+
#include <utility>
10+
#include <unordered_map>
11+
#include <string>
12+
#include <sstream>
13+
14+
constexpr int NUM_TESTCASES = 10;
15+
16+
using namespace std::string_literals;
17+
18+
long long dynamicProgramming(const std::vector<long long>& prices){
19+
int n = prices.size();
20+
int m = n / 2; // Maximum Long Position
21+
const long long INF = 1e15; // A large value to represent infinity
22+
23+
std::vector<std::vector<long long>> dp(n + 1, std::vector<long long>(m + 1, -INF));
24+
// dp[i][j] represents the maximum profit by the end of day i with j long positions held (i is 1-indexed)
25+
26+
dp[0][0] = 0; // On day 0, no profit and no positions held
27+
28+
for (int i = 1; i <= n; ++i){
29+
for (int j = 0; j <= m; ++j){
30+
dp[i][j] = dp[i - 1][j]; // do nothing on day i
31+
if (j > 0){
32+
// Buy on day i
33+
dp[i][j] = std::max(dp[i][j], dp[i - 1][j - 1] - prices[i - 1]);
34+
}
35+
if (j < m && i > 1){
36+
// Sell on day i
37+
dp[i][j] = std::max(dp[i][j], dp[i - 1][j + 1] + prices[i - 1]);
38+
}
39+
}
40+
}
41+
42+
return dp[n][0]; // Return the maximum profit with no risk exposure
43+
}
44+
45+
long long regretableGreedy(const std::vector<long long>& prices){
46+
std::priority_queue<long long, std::vector<long long>, std::greater<long long>> minHeap;
47+
long long profit = 0;
48+
49+
for (auto price : prices){
50+
if (!minHeap.empty() && price > minHeap.top()){
51+
profit += price - minHeap.top(); // Sell at current price, buy at minimum price seen so far
52+
minHeap.pop(); // Remove the minimum price from the heap
53+
minHeap.push(price); // Add extra option to allow us to regret this decision
54+
}
55+
minHeap.push(price); // Add current price to the heap
56+
}
57+
58+
return profit;
59+
}
60+
61+
std::vector<int> splitTestcaseSizes(int tc, std::mt19937& rng){
62+
std::vector<int> sizes;
63+
64+
std::uniform_int_distribution<int> cutDistribution(0, 300000 - tc);
65+
66+
std::vector<int> cuts;
67+
cuts.push_back(0);
68+
cuts.push_back(300000 - tc);
69+
70+
for (int i = 1 ; i < tc ; ++i){
71+
cuts.push_back(cutDistribution(rng));
72+
}
73+
74+
std::sort(cuts.begin(), cuts.end());
75+
76+
for (int i = 1 ; i < cuts.size() ; ++i){
77+
sizes.push_back(cuts[i] - cuts[i - 1] + 1);
78+
}
79+
80+
assert(sizes.size() == tc);
81+
assert(std::accumulate(sizes.begin(), sizes.end(), 0) == 300000);
82+
83+
return sizes;
84+
}
85+
86+
// The testcases are generates in pairs of prompt (input) as a string
87+
// and the solutions, which is a list of long long integers.
88+
std::pair<std::string, std::vector<long long>> generateTestcases(std::mt19937& rng){
89+
auto sizes = splitTestcaseSizes(NUM_TESTCASES, rng);
90+
91+
std::stringstream input;
92+
std::vector<long long> expectedOutput;
93+
94+
input << NUM_TESTCASES << "\n";
95+
96+
std::uniform_int_distribution<long long> priceDistribution(1, 1'000'000);
97+
98+
bool sortedTestcase = false;
99+
100+
for (auto size : sizes){
101+
input << size << "\n";
102+
std::vector<long long> prices(size);
103+
for (int i = 0; i < size; ++i){
104+
prices[i] = priceDistribution(rng);
105+
}
106+
107+
if (!sortedTestcase){
108+
std::sort(prices.begin(), prices.end());
109+
sortedTestcase = true;
110+
}
111+
112+
for (const auto& price : prices){
113+
input << price << " ";
114+
}
115+
input << "\n";
116+
117+
long long greedyResult = regretableGreedy(prices);
118+
if (size <= 10000){
119+
assert(greedyResult == dynamicProgramming(prices) && "Verifier's Greedy solution failed!");
120+
}
121+
122+
expectedOutput.push_back(greedyResult);
123+
}
124+
125+
return { input.str(), expectedOutput };
126+
}
127+
128+
int main(int argc, char* argv[]){
129+
/*
130+
Usage:
131+
- For `fuzz`-ing: `./time-traveler generate <seed>` will print prompt to std::cout.
132+
- For `judge`-ing: `./time-traveler verify <seed>` will read lines from std::cin.
133+
- For extra solution validation, you can also use `./time-traveler solution <seed>`.
134+
*/
135+
136+
// A string is given as a seed, which is passed to the hasher.
137+
// The RNG is then initialised based on this seed, and is thus
138+
// passed as references to each of the methods rather than being a global variable.
139+
std::hash<std::string> hasher;
140+
std::mt19937 rng(hasher(argv[2]));
141+
142+
auto [prompt, answers] = generateTestcases(rng);
143+
144+
if (argv[1] == "generate"s){
145+
std::cout << prompt;
146+
return 0;
147+
}
148+
else if (argv[1] == "verify"s){
149+
// To retrieve submitted answers, query each line one by one until no more lines are found.
150+
std::vector<long long> submittedAnswers;
151+
152+
std::string line;
153+
std::getline(std::cin, line);
154+
for (int i = 0; !line.empty(); ++i, std::getline(std::cin, line)){
155+
try {
156+
submittedAnswers.push_back(std::stoll(line));
157+
} catch (const std::invalid_argument& error){
158+
std::cerr << "Line " << (i + 1) << " of your answer is not a valid integer.\n";
159+
return 1;
160+
} catch (const std::out_of_range& error){
161+
std::cerr << "Line " << (i + 1) << " contains an invalid 64-bit integer.\n";
162+
return 1;
163+
}
164+
}
165+
166+
// Compare the submitted answers with the expected answers.
167+
if (submittedAnswers == answers){
168+
return 0;
169+
}
170+
else if (submittedAnswers.size() != answers.size()){
171+
std::cerr << "Wrong solution format: there should be " << NUM_TESTCASES << " lines.\n";
172+
return 1;
173+
}
174+
else{
175+
std::cerr << "Wrong answer\n";
176+
return 1;
177+
}
178+
}
179+
else if (argv[1] == "solution"s){
180+
for (long long answer : answers){
181+
std::cout << answer << "\n";
182+
}
183+
return 0;
184+
}
185+
else{
186+
return 1;
187+
}
188+
}

0 commit comments

Comments
 (0)