#include<iostream>
#include<vector>
#include<queue>
using namespace std;
// find
int find(vector<int>& U, int a) {
while (a != U[a])
a = U[a];
return a;
}
// find + two pass path compression
int find2(vector<int>& U, int a) {
int root = a;
while (root != U[root])
root = U[root];
int next = U[a];
while (next != root) {
U[a] = root;
a = next;
next = U[a];
}
return root;
}
// union
void merge(vector<int>& U, int a, int b) {
U[find(U,b)] = find(U, a);
}
int main() {
int N;
long long s;
cin >> N >> s;
vector<long long> x(N + 1);
vector<int> p(N + 1);
vector<long long> cost_to_take(N + 1);
vector<int> U(N + 1);
for (int i = 0;i < N + 1;++i) {
U[i] = i;
}
auto cmp = [&](int a, int b) {return cost_to_take[a] > cost_to_take[b];}; // comparator for "least expensive jobs first" behavior by priority queue
priority_queue<int, std::vector<int>, decltype(cmp)> pq(cmp);
x[0] = s;
p[0] = 0;
for (int i = 1;i < N + 1; ++i) {
cin >> x[i] >> p[i];
if (x[i] > 0) {
pq.push(i); // only jobs with positive profit are worth merging up
}
else {
cost_to_take[i] = -x[i]; // -x(i) is the cost to take a job with non-positive profit, i.e. x(i) <= 0
}
}
while (!pq.empty()) {
int least_expensive_job = pq.top(); // least expensive job; this job's profit is always positive because only positive profit jobs are pushed onto the priority queue
pq.pop();
int least_expensive_job_parent = find2(U, p[least_expensive_job]); // least expensive job's parent job
if (cost_to_take[least_expensive_job_parent] + x[least_expensive_job_parent] >= cost_to_take[least_expensive_job]) {
// Case 1: if this job's parent were to be taken, there would at the very least be cost_to_take[job's parent] + x[job's parent] euros to take children jobs after.
// So, if this job costs less than, then it should be taken/merged. Remember, it's profit is positive.
// Note, that the very first jobs ever pushed onto the priority queue will cost 0 to take, so they'll all get merged.
merge(U, least_expensive_job_parent, least_expensive_job); // merge jobs
x[least_expensive_job_parent] += x[least_expensive_job]; // add profits
x[least_expensive_job] = 0;
}
else {
// Case 2: What does it mean if all case 1 job merges are exhausted? It means that cost_to_take[job's parent] + x[job's parent] < cost_to_take[job] is true for all jobs now.
// That means that the next merge will certainly increase the parent job's cost to take. Greedily picking the least expensive positive profit job (case 1 before case 2) brings about the smallest increase in the next merged job's cost to take.
// However, tt turns out that greedily always picking the least expensive positive profit job to take is enough, even though it may not fully exhaust the case 1 jobs, in which ... >= cost_to_take[job]).
// But it does not matter because there is never a case where taking the least costly to take positive profit job unduly prempts the taking of another or raises its parents cost to take more than it should.
if (least_expensive_job_parent != 0) {
cost_to_take[least_expensive_job_parent] = cost_to_take[least_expensive_job] - x[least_expensive_job_parent];
merge(U, least_expensive_job_parent, least_expensive_job); // merge jobs
x[least_expensive_job_parent] += x[least_expensive_job]; // add profits
x[least_expensive_job] = 0;
}
}
if (least_expensive_job_parent != 0 && x[least_expensive_job_parent] > 0) { // the root job has no parent job to merge with and only positive jobs are worth merging
pq.push(least_expensive_job_parent);
}
}
cout << x[0] - s << endl;
}
| # | Verdict | Execution time | Memory | Grader output |
|---|
| Fetching results... |
| # | Verdict | Execution time | Memory | Grader output |
|---|
| Fetching results... |
| # | Verdict | Execution time | Memory | Grader output |
|---|
| Fetching results... |
| # | Verdict | Execution time | Memory | Grader output |
|---|
| Fetching results... |
| # | Verdict | Execution time | Memory | Grader output |
|---|
| Fetching results... |