Skip to Content

Timeismoney

Análisis oficial 

Explicación

Para los primeros casos de prueba en los que t=ct=c, podemos resolver el problema construyendo un MST con tt como pesos. Si hacemos T=tT = \sum t y C=cC = \sum c, V=TC=T2V = T \cdot C = T^2 se minimizará cuando TT se minimice.

Para resolver el problema por completo, hay que minimizar el producto de esas dos sumas, V=CTV = C \cdot T. El espacio de soluciones contiene todos los MST posibles con sus respectivos valores de TT y CC. Podemos ver cada una de esas soluciones posibles como un punto 2D (T,C)(T, C). Existe una curva C(T)=VTC(T) = \frac{V}{T} tal que todo punto sobre ella tiene el mismo valor de VV. Como función de proporción inversa, es convexa, así que la mejor solución debe estar en algún lugar de la curva más a la izquierda, es decir, sobre la envolvente convexa inferior. Por lo tanto, basta revisar todos los puntos de la envolvente convexa inferior de nuestro espacio de soluciones para hallar el valor mínimo de VV. No obstante, no podemos calcular realmente la envolvente convexa porque requeriría conocer los (MN1)\binom{M}{N-1} puntos. En su lugar, usamos el enfoque de abajo.

Al comienzo, elegimos dos puntos de la envolvente convexa inferior, AA y BB, como puntos de partida de la búsqueda. AA debe ser el punto donde minimizamos TT y BB donde minimizamos CC. Luego queremos hallar un punto PP que esté lo más a la izquierda posible de la recta ABAB. El área del triángulo ABPABP es proporcional a la distancia entre la recta ABAB y el punto PP, dado que AB|AB| es constante. Por lo tanto, solo hay que hallar el triángulo ABPABP de área máxima. Usando el producto cruz, obtenemos 2AABP=AB×AP=(xBxA)(yPyA)(yByA)(xPxA)=yP(xBxA)xP(yByA)+xA(yByA)yA(xBxA)-2A_{ABP} = AB \times AP = (x_B - x_A)(y_P - y_A) - (y_B - y_A)(x_P - x_A) = y_P(x_B - x_A) - x_P(y_B - y_A) + x_A(y_B - y_A) - y_A(x_B - x_A). Nótese que xA(yByA)yA(xBxA)x_A(y_B - y_A) - y_A(x_B - x_A) es constante y se puede ignorar. Así, AABP-A_{ABP} es proporcional a yP(xBxA)xP(yByA)y_P(x_B - x_A) - x_P(y_B - y_A). Como nuestro objetivo era maximizar AABPA_{ABP}, aquí debemos minimizar yP(xBxA)xP(yByA)y_P(x_B - x_A) - x_P(y_B - y_A) en su lugar. Esto se puede hacer tomando este valor como el peso de nuestro algoritmo de MST y luego construyendo el MST. El resultado sería nuestro nuevo punto PP. Si PP está del lado derecho del vector ABAB (y por lo tanto del lado izquierdo de la recta ABAB), también está en la envolvente convexa. Luego usamos divide y vencerás y resolvemos el mismo problema con los puntos AA, PP o PP, BB. Para cada uno de estos puntos, actualizamos la mejor solución actual si el nuevo punto es mejor. En caso contrario, si PP está del lado derecho de la recta ABAB, no hace falta seguir buscando con PP porque ya no está en la envolvente convexa inferior.

Como TT y CC son enteros positivos, la envolvente convexa inferior entre AA y BB consiste en k(N1)tmax=50745k \leq (N-1) \cdot t_{max} = 50745 puntos. Para cada uno de esos kk puntos, corremos el algoritmo de Kruskal en O(MlogM)\mathcal{O}(M \log M). Por lo tanto, la complejidad temporal total es O(kMlogM)\mathcal{O}(kM \log M). Esto alcanza para los casos de prueba dados.

Implementación

#include <bits/stdc++.h> using namespace std; using ll = long long int; using pii = pair<int, int>; // BeginCodeSnip{DSU} struct DSU { vector<int> g; DSU(int n) : g(n, -1) {} int find(int a) { return g[a] < 0 ? a : (g[a] = find(g[a])); } /** * If a and b are already in the same set, return false and do nothing. * Otherwise, merge set a and b into one set and return true. */ bool unite(int a, int b) { a = find(a); b = find(b); if (a == b) return false; if (g[a] > g[b]) swap(a, b); g[a] += g[b]; g[b] = a; return true; } }; // EndCodeSnip struct Edge { int x; int y; int time; int cost; int id; }; /** * Kruskal's Algorithm to find a MST with given edges and weights. Returns the * time and cost as pair<int,int> as well as ids of edges of the MST as * vector<int>. */ pair<pii, vector<int>> kruskal(int n, vector<Edge> &edges, vector<int> &weights) { sort(begin(edges), end(edges), [&](const Edge &e1, const Edge &e2) { return weights[e1.id] < weights[e2.id]; }); DSU dsu(n); vector<int> mst; int t = 0; int c = 0; for (Edge &e : edges) { if (dsu.unite(e.x, e.y)) { t += e.time; c += e.cost; mst.push_back(e.id); } } return {{t, c}, mst}; } /** * Calculate the signed area of the triangle defined by the given points. * This value is negative if point P is on the right side of vector AB. */ ll area(pii A, pii B, pii P) { pii AB = {B.first - A.first, B.second - A.second}; pii AP = {P.first - A.first, P.second - A.second}; return AB.first * AP.second - AB.second * AP.first; } int main() { int N, M; cin >> N >> M; vector<Edge> edges(M); for (int i = 0; i < M; i++) { int x, y, t, c; cin >> x >> y >> t >> c; edges[i] = {x, y, t, c, i}; } // The best Minimum Spanning Tree found so far. vector<int> best_MST; // The sum of time and cost w.r.t. best_MST. pii best_V = {1e8, 1e8}; vector<int> current_MST; pii A, B, P; // The weights used by Kruskal's Algorithm to determine the MST. vector<int> weights(M); // A corresponds to the values of the MST if we only minimize the time for (int i = 0; i < M; i++) { weights[edges[i].id] = edges[i].time; } tie(A, best_MST) = kruskal(N, edges, weights); best_V = A; // B corresponds to values of the MST if we only minimize the cost for (int i = 0; i < M; i++) { weights[edges[i].id] = edges[i].cost; } tie(B, current_MST) = kruskal(N, edges, weights); // If solution B is better than A, update the best solution if ((ll)B.first * B.second < (ll)A.first * A.second) { best_V = B; best_MST = current_MST; } // Search recursively for points on lower convex hull of the solution space stack<pair<pii, pii>> to_search; to_search.push({A, B}); while (!to_search.empty()) { auto [A, B] = to_search.top(); to_search.pop(); /* * Since P is on the right side of vector AB, the signed area is * negative. Thus, to maximize the unsigned area of triangle ABP, we * should minimize this signed value. */ for (int i = 0; i < M; i++) { weights[edges[i].id] = (edges[i].cost * (B.first - A.first) - edges[i].time * (B.second - A.second)); } tie(P, current_MST) = kruskal(N, edges, weights); /* * If P is on the left side of vector AB, we can terminate the search * on this branch. Note that the "left side of vector AB" is the right * side on the coordinate system. */ if (area(A, B, P) >= 0) { continue; } // Compare the V value and update the best result if needed. if ((ll)P.first * P.second < (ll)best_V.first * best_V.second) { best_V = P; best_MST = current_MST; } /* * Search further with the new acquired point P to see if there is * another point that is on a hyperbola further left than P. */ to_search.push({A, P}); to_search.push({P, B}); } cout << best_V.first << " " << best_V.second << "\n"; sort(begin(edges), end(edges), [](const Edge &e1, const Edge &e2) { return e1.id < e2.id; }); for (int &id : best_MST) { cout << edges[id].x << " " << edges[id].y << "\n"; } }