/** * Solution to IOI 2009 problem "salesman" * * We use a C++ set to store the list of currently "active" fairs, * where an active fair is one that has a better profit than by * visiting the fair than reaching the same location from some * other fair. We then process the fairs in order of the time at * which they occur, using our active set to find the best fair * to come from in order to visit each new successive fair, and * updating the active set with the new fair afterwards. * * For fairs on the same day, we note that the most efficient * way of visiting these is to pick one as a starting point and * then visit fairs in a single direction. We test both the * upstream and downstream directions, and for each fair decide * whether it is better to go directly to this fair or to come * from the previous fair opposite to the direction in which * we are travelling. * * Carl Hultquist, chultquist@gmail.com */ #include #include #include #include #include #include #include using namespace std; #define MAX_N 500000 #define MAX_D_U 10 #define MAX_S 500001 #define MAX_T 500000 #define MAX_L 500001 #define MAX_M 4000 int n, d, u, s; // Class for storing all the things we want to associate with a fair typedef struct fair { int t, l, m; int best; // Used for traversing upstream and downstream when fairs occur // on the same day. int baseBest; int curBest; // Order fairs first according to time, and secondly according // to their location (which makes the upstream and downstream // traversals much easier). bool operator <(const fair &f) const { if (t < f.t) return true; else if (t == f.t && l < f.l) return true; else return false; } } fair; // For our set, we want to order fairs by location only, so have // a special comparator for this. struct location_less { bool operator ()(const fair &x, const fair &y) { return x.l < y.l; } }; vector fairs; set activeFairs; /** * Computes the cost of travelling between two points on the river. */ double travelCost(double from, double to) { if (from < to) return (to - from) * d; else return (from - to) * u; } /** * Queries for the best profit that we can expect to earn when * visiting fair i directly from some fair that occurred on a * previous day. * * We find the active fairs that occur to the left and right of * this one along the river -- it is fairly easy to show that our * optimal predecessor must be one of these fairs. Then we pick * the one which gives us a greater profit. */ int query(int i) { set::iterator low = activeFairs.lower_bound(fairs[i]); set::iterator high = low; if (low != activeFairs.begin()) low--; if (high == activeFairs.end()) high--; return max(low->best - travelCost(low->l, fairs[i].l), high->best - travelCost(high->l, fairs[i].l)) + fairs[i].m; } /** * Updates our active fairs set with the given fair. Care must be * taken to ensure that this fair is actually active (if all fairs * are on distinct days then it will be, but if there are several on * the same day then there may be a better path to this location on * the river via some other fair). */ void update(int i) { // Insert by default, we will remove it later if we need to. activeFairs.insert(fairs[i]); set::iterator thisFair = activeFairs.lower_bound(fairs[i]); set::iterator cur = thisFair; // Check upstream fairs while (cur != activeFairs.begin()) { cur--; // Check if the fair we have just added is covered by the // previous fair in the set. if (cur->best - travelCost(cur->l, thisFair->l) >= thisFair->best) { // Yes, so remove and return activeFairs.erase(thisFair); return; } // Check if the fair we have just added covers the previous // fair in the set. if (thisFair->best - travelCost(thisFair->l, cur->l) < cur->best) // No, so stop removing non-active fairs break; // Remove the fair that is covered by our recently added one, // and which is thus no longer active. activeFairs.erase(cur); cur = thisFair; } // Check downstream fairs cur = thisFair; cur++; while (cur != activeFairs.end()) { // Check if the fair we have just added is covered by the // next fair in the set. if (cur->best - travelCost(cur->l, thisFair->l) >= thisFair->best) { // Yes, so remove and return activeFairs.erase(thisFair); return; } // Check if the fair we have just added covers the next // fair in the set. if (thisFair->best - travelCost(thisFair->l, cur->l) < cur->best) break; // Remove the fair that is covered by our recently added one, // and which is thus no longer active. activeFairs.erase(cur); cur = thisFair; cur++; } } int main() { scanf("%d %d %d %d", &n, &u, &d, &s); assert(1 <= n && n <= MAX_N); assert(1 <= d && d <= MAX_D_U); assert(1 <= u && u <= MAX_D_U); assert(1 <= s && s <= MAX_S); fairs.resize(n + 2); int maxT = 0; for (int i = 1; i <= n; i++) { scanf("%d %d %d", &fairs[i].t, &fairs[i].l, &fairs[i].m); assert(1 <= fairs[i].t && fairs[i].t <= MAX_T); assert(1 <= fairs[i].l && fairs[i].l <= MAX_L); assert(1 <= fairs[i].m && fairs[i].m <= MAX_M); maxT = max(maxT, fairs[i].t); } // Create a dummy fairs for the salesman's home at the beginning, // and also one at the end fairs[0].l = s; fairs[0].best = 0; fairs[0].t = -1; fairs[n + 1].l = s; fairs[n + 1].m = 0; fairs[n + 1].t = maxT + 1; sort(fairs.begin(), fairs.end()); // At the beginning, only fair 0 (the salesman's home) is active activeFairs.insert(fairs[0]); for (int i = 1; i <= n; i++) { // Check to see if we have multiple fairs on the same day if (i == n || fairs[i].t != fairs[i + 1].t) { // No, so just do the simple approach of querying for // the best fair to come from in order to reach this // one, and update our data structure. fairs[i].best = query(i); update(i); } else { // Yes, there are multiple fairs happening on this day. We need // to choose the best way of picking which of these fairs to // visit. int first = i; while (i <= n && fairs[i].t == fairs[first].t) i++; i--; int last = i; // First, find the cheapest way of reaching each fair on this // day if we visit no other fairs on the same day. for (int j = first; j <= last; j++) { fairs[j].baseBest = query(j); fairs[j].best = fairs[j].baseBest; } // Now visit each of the fairs in both an upstream and // downstream direction, in each case seeing if it was // better to come via the previous fair. fairs[first].curBest = fairs[first].baseBest; for (int j = first + 1; j <= last; j++) { // See if it is better to come from the previous // fair. int profit = fairs[j - 1].curBest - travelCost(fairs[j - 1].l, fairs[j].l) + fairs[j].m; if (profit > fairs[j].baseBest) fairs[j].curBest = profit; else fairs[j].curBest = fairs[j].baseBest; if (fairs[j].curBest > fairs[j].best) fairs[j].best = fairs[j].curBest; } // And upstream. Don't forget to start off with the base // value of visiting this fair directly (otherwise we will // use values fro the downstream pass, which will be // confusing). fairs[last].curBest = fairs[last].baseBest; for (int j = last - 1; j >= first; j--) { // See if it is better to come from the previous // fair. int profit = fairs[j + 1].curBest - travelCost(fairs[j + 1].l, fairs[j].l) + fairs[j].m; if (profit > fairs[j].baseBest) fairs[j].curBest = profit; else fairs[j].curBest = fairs[j].baseBest; if (fairs[j].curBest > fairs[j].best) fairs[j].best = fairs[j].curBest; } // Update our query structure for all of these fairs, and // see if there is a better way home from any of them. for (int j = first; j <= last; j++) update(j); } } int best = query(n + 1); // Check that our profit is positive; if not then output 0 // (indicating that the salesman simply stays at home for the // year). cout << (best < 0 ? 0 : best) << endl; return 0; }