@description@

有n个城市(编号从0..n-1),m条公路(双向的),从中选择n-1条边,使得任意的两个城市能够连通,一条边需要的c的费用和t的时间,定义一个方案的权值 v = (n-1条边的费用和)*(n-1条边的时间和),你的任务是求一个方案使得v最小。

Input

第一行两个整数n,m,接下来每行四个整数a,b,c,t,表示有一条公路从城市a到城市b需要t时间和费用c

Output

仅一行两个整数sumc,sumt,(sumc表示使得v最小时的费用和,sumc表示最小的时间和) 如果存在多个解使得sumc*sumt相等,输出sumc最小的

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 501

HINT

【数据规模】

1<=N<=200, 1<=m<=10000, 0<=a,b<=n-1, 0<=t,c<=255。

有5%的数据m=n-1

有40%的数据有t=c

对于100%的数据如上所述

@solution@

非常经典的题.jpg。

假如将 (sumc, sumt) 看成一个坐标,那么一个可行生成树方案对应了坐标系中的一个点。

可以发现:只有下凸包上的点才可能成为答案。

如果不在下凸包上,那么可以作原点到该点的直线,它与凸包的交点显然更优。

同时,一条线段肯定取两个端点得到的乘积最小(写出表达式发现是二次函数)。

因为三点共线肯定不优,所以凸包上的斜率互不相同且递减。因此,凸包上的点最多只有 \(O(\sqrt{N*\max\{c, t\}})\) 个点。

我们只需要尝试找出凸包上的点并更新答案即可。

考虑两个必定在凸包内的点 A(minx, y) 与 B(x, miny)(不可能找到更大的凸包严格包含这两个点)。

如果忽视掉下凸包中斜率为正的部分(这部分肯定不优),这两个点就是凸包上的点横纵坐标的边界。

我们根据直线 AB,找该直线在凸包上对应的切线,就可以又找到一个新的凸包上的点。

其实就是距离 AB 最远的点 C。距离最远可以转成 ABC 的面积最大,然后可以写出叉积。

根据叉积式子再做一遍最大生成树(注意是最大)就可以找到 C 了。

然后分治 AC, CB 即可。

时间复杂度的一个上界为 \(O(\sqrt{N*\max\{c, t\}}*M\log M)\)。

@accepted code@

#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);
}

@details@

其实这个模型之所以经典,是因为它的可迁移性很强。

比如给你整一个下一次最小乘积最短路,最小乘积最小割之类的。

另外,我们 01 分数规划 + 最小生成树,尽管平时用的是二分,其实也可以采用几何方法来做。

但是复杂度就很玄妙了。

最新文章

  1. Spark Streaming源码解读之JobScheduler内幕实现和深度思考
  2. hdu4609 3-idiots
  3. 在git 服务器挂载新的项目
  4. 菜鸟学习Hibernate——简单的一个例子
  5. 用Swift重写公司OC项目(Day2)--创建OC与Swift的桥接文件,进而调用OC类库
  6. HTML5 Canvas 2D绘图
  7. 结构-行为-样式-angularJs ngAnimate(Js实现)
  8. poi的各种单元格样式以及一些常用的配置
  9. HDU5916
  10. 简易js进度条
  11. android布局##TableLayout和FrameLayout-android学习之旅(十五)
  12. Visual Studio Community 2013,功能完整,免费使用
  13. 为什么有那么多人愿意喝&quot;鸡汤&quot;?
  14. 移动端1px问题处理方法
  15. 通过Socket实现TCP编程,用户登录之服务器相应客户端,客户端和服务端之间的通信
  16. 搭建RISC-V错误记录
  17. CentOS7通过rsync+crontab实现两台服务器文件同步
  18. C# Command命令(行为型模式)+队列 实现事务,带异步命令重试机制和生命周期
  19. Git 配置命令设置
  20. Opatch使用细则

热门文章

  1. H5C3--过渡transition
  2. 读书笔记--Head First 面向对象分析与设计 目录
  3. Mac Eclipse上Android SDK manager闪退的问题!!
  4. python基础--闭包and装饰器
  5. C#基础之Async和Await 的异步编程
  6. oracle-视图-索引-序列
  7. 初探Druid
  8. 洛谷 P2356 弹珠游戏
  9. Django与HTML业务基本结合--基本的用户名密码提交方法2
  10. 注解2 --- 自定义 Annotation --- 技术搬运工(尚硅谷)