有n個城市(編號從0..n-1),m條公路(雙向的),從中選擇n-1條邊,使得任意的兩個城市可以連通,一條邊須要的c的費用和t的時間,定義一個方案的權值 v = (n-1條邊的費用和)*(n-1條邊的時間和),你的任務是求一個方案使得v最小。spa
Input
第一行兩個整數n,m,接下來每行四個整數a,b,c,t,表示有一條公路從城市a到城市b須要t時間和費用c
Output
僅一行兩個整數sumc,sumt,(sumc表示使得v最小時的費用和,sumc表示最小的時間和) 若是存在多個解使得sumc*sumt相等,輸出sumc最小的code
Sample Input
5 7
0 1 161 79
0 2 161 15
0 3 13 153
1 4 142 183
2 4 236 80
3 4 40 241
2 1 65 92
Sample Output
279 501ip
HINT
【數據規模】
1<=N<=200, 1<=m<=10000, 0<=a,b<=n-1, 0<=t,c<=255。
有5%的數據m=n-1
有40%的數據有t=c
對於100%的數據如上所述get
很是經典的題.jpg。it
假如將 (sumc, sumt) 當作一個座標,那麼一個可行生成樹方案對應了座標系中的一個點。io
能夠發現:只有下凸包上的點纔可能成爲答案。
若是不在下凸包上,那麼能夠做原點到該點的直線,它與凸包的交點顯然更優。
同時,一條線段確定取兩個端點獲得的乘積最小(寫出表達式發現是二次函數)。class
由於三點共線確定不優,因此凸包上的斜率互不相同且遞減。所以,凸包上的點最多隻有 \(O(\sqrt{N*\max\{c, t\}})\) 個點。
咱們只須要嘗試找出凸包上的點並更新答案便可。date
考慮兩個一定在凸包內的點 A(minx, y) 與 B(x, miny)(不可能找到更大的凸包嚴格包含這兩個點)。
若是忽視掉下凸包中斜率爲正的部分(這部分確定不優),這兩個點就是凸包上的點橫縱座標的邊界。方法
咱們根據直線 AB,找該直線在凸包上對應的切線,就能夠又找到一個新的凸包上的點。
其實就是距離 AB 最遠的點 C。距離最遠能夠轉成 ABC 的面積最大,而後能夠寫出叉積。
根據叉積式子再作一遍最大生成樹(注意是最大)就能夠找到 C 了。
而後分治 AC, CB 便可。
時間複雜度的一個上界爲 \(O(\sqrt{N*\max\{c, t\}}*M\log M)\)。
#include <cstdio> #include <algorithm> using namespace std; const int MAXN = 200; const int MAXM = 10000; const int INF = int(1E9); typedef long long ll; int n, m; struct point{ int c, t; ll k; point(int _c=0, int _t=0) : c(_c), t(_t), k(1LL*_c*_t) {} friend point operator + (point a, point b) { return point(a.c + b.c, a.t + b.t); } }ans(INF, INF); void update(point p) { if( p.k < ans.k || (p.k == ans.k && p.c < ans.c) ) ans = p; } int a, b; struct edge{ int u, v; point p; ll get() {return 1LL*a*p.c + 1LL*b*p.t;} friend bool operator < (edge a, edge b) { return a.get() < b.get(); } }e[MAXM + 5]; int fa[MAXN + 5]; int find(int x) { return fa[x] = (fa[x] == x ? x : find(fa[x])); } point get() { for(int i=1;i<=n;i++) fa[i] = i; sort(e + 1, e + m + 1); point ret(0, 0); for(int i=1;i<=m;i++) { int fu = find(e[i].u), fv = find(e[i].v); if( fu != fv ) { ret = ret + e[i].p; fa[fu] = fv; } } return ret; } void solve(point A, point B) { a = (A.t - B.t), b = (B.c - A.c); point C = get(); if( a*C.c + b*C.t + (B.t - A.t)*B.c + (A.c - B.c)*B.t >= 0 ) return ; update(C), solve(A, C), solve(C, B); } int main() { scanf("%d%d", &n, &m); for(int i=1;i<=m;i++) { scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].p.c, &e[i].p.t); e[i].u++, e[i].v++; } a = 1, b = 0; point A = get(); update(A); a = 0, b = 1; point B = get(); update(B); solve(A, B); printf("%d %d\n", ans.c, ans.t); }
其實這個模型之因此經典,是由於它的可遷移性很強。
好比給你整一個下一次最小乘積最短路,最小乘積最小割之類的。
另外,咱們 01 分數規劃 + 最小生成樹,儘管平時用的是二分,其實也能夠採用幾何方法來作。 可是複雜度就很玄妙了。