紫书 例题 11-14 UVa 1279 (动点最小生成树)(详细解释)
这道题写了好久……
在三维空间里面有动的点, 然后求有几次最小生成树。
其实很容易发现, 在最小生成树切换的时候,在这个时候一定有两条边相等,
而且等一下更大的那条边在最小生成树中,等一下更小的边不在最小生成树中。
这样的话过了这个时刻,等一下更小的边就会代替等一下更大的边, 从而最小生成树切换
然后我们讨论怎么实现
第一步, 建边
因为这里边的长度是随时间变化的, 所以我们可以把其写成一个二次函数。
那么显然根据两点间距离公式, 长度的平方等于x方向距离的平方+y方向距离的平方+z方向距离的平方
我们假设两个点i j
那么x方向距离的平方为当前两点x坐标相减的平方
当前的x坐标为原来的x坐标加上后来走的距离
也就是(dx是速度, x是原来的x坐标)
(edges[i].dx-edges[j].dx) * t + (edges[i].x-edges[j].x))^2
那么展开就可以得到at^2 + bt + c这样的式子
那么y方向和z方向也一样。
最终的a是三个方向的a加起来, b和c也一样
所以最后结果是这样
#define f1(a) (point[i].a - point[j].a)
#define f2(a) pow(f1(a), 2)
a =f2(dx) + f2(dy) + f2(dz)
b = 2 * (f1(dx) * f1(x) + f1(dy) * f1(y) + f1(dz) * f1(z))
c = f2(x) + f2(y) + f2(z)
然后当前边再记录起点终点。
然后我们根据开始时候的长度从小到大来排序, 等价于at^2 + bt + c中的c。
第二步, 建立事件
事件也就是开头所讲的边长度相等的时刻。
这些事件不一定都能切换最小生成树, 但切换最小生成树一定在这些事件当中。
那么怎么建立事件呢?
就暴力枚举每两条边之间是否会在某一时刻相等, 若相等则是一个事件
那么判断是否相等就是解一元二次方程了
数学问题
事件记录的是时间, 新边, 旧边
这里要注意区分新边和旧边
代码里面有注释, 这里不讲了。
第三步, 判断切换次数, 也就是答案
首先先做第一次最小生成树, 后面会切换
然后去判断新边能否代替旧的边
可以的话就更新答案。
具体看代码,注意解方程那一段最好拿纸画一下图像,因为一定要清楚哪个是
旧的边哪个是新的边,不能弄错。
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int MAXN = 60;
const int MAXM = MAXN * (MAXN + 1) / 2;
const double EPS = 1e-8; //题目中最小单位是10^-6
struct Point
{
double x, y, z, dx, dy, dz;
void read() { scanf("%lf%lf%lf%lf%lf%lf", &x, &y, &z, &dx, &dy, &dz); }
}point[MAXN];
struct Edge
{
double a, b, c;
int u, v;
bool operator < (const Edge& rhs) const
{
return c < rhs.c;
}
};
vector<Edge> edges;
struct Event
{
double t;
int newe, olde;
bool operator < (const Event& rhs) const
{
return t < rhs.t;
}
};
vector<Event> events;
int f[MAXN], n;
#define f1(a) (point[i].a - point[j].a)
#define f2(a) pow(f1(a), 2)
void make_edge() //建边
{
edges.clear();
REP(i, 0, n)
REP(j, i + 1, n)
edges.push_back(Edge{f2(dx) + f2(dy) + f2(dz), 2 * (f1(dx) * f1(x) + f1(dy) * f1(y) + f1(dz) * f1(z)), f2(x) + f2(y) + f2(z), i, j}); //见解析
sort(edges.begin(), edges.end());
}
#define d(x) edges[s1].x - edges[s2].x
void make_event()
{
events.clear();
REP(i, 0, edges.size())
REP(j, i + 1, edges.size()) //以下是解二元一次方程
{
int s1 = i, s2 = j;
if(edges[s1].a < edges[s2].a) swap(s1, s2); //这里的s1的新边,也就是等一下会小于旧边 。规定a > 0方便后面计算
double a = d(a), b = d(b), c = d(c); //解方程中的移项
if(fabs(a) < EPS) //a等于0,也就是说变成一元一次方程
{
if(fabs(b) < EPS) continue; //b=0, 那么c = 0,无解(c是原来边的长度, 肯定不等于0)
if(b > 0) swap(s1, s2), b = -b, c = -c; // 规定一下b < 0,可以结合一次函数图像
if(c > 0) events.push_back(Event{-c / b, s1, s2});//这样再过一会儿函数值小于0,s1 - s2 < 0
continue; //所以s1是小的,是新边。 当b < 0时,只有c > 0时t的值才为正
}
double delta = b * b - 4 * a * c; //判别式
if(delta < EPS) continue; //判别式小于0没有实根
delta = sqrt(delta);
double t1 = (-b - delta) / (2 * a); //图像与x轴的左交点
double t2 = (-b + delta) / (2 * a); //图像与x轴的右交点
if(t1 > 0) events.push_back(Event{t1, s1, s2}); //注意前面规定了 a > 0 如果是左交点, 那么再过一会儿
if(t2 > 0) events.push_back(Event{t2, s2, s1}); //函数值小于0,也就是s1-s2 < 0, 也就是说s1会更小,
// 也就是新的边。同理右交点,s2会更小,那么s2才是新的边
}
sort(events.begin(), events.end());
}
int find(int x)
{
if(f[x] != x)
f[x] = find(f[x]);
return f[x];
}
int solve()
{
int pos[MAXM], e[MAXN], num = 0; //pos[i]表示第i条边在最小生成树中是第几条边, 值为0表示不在最小生成树中
REP(i, 0, n) f[i] = i; //e[i]表示最小生成树中的第i条边的编号
REP(i, 0, edges.size()) pos[i] = 0;
REP(i, 0, edges.size()) //先做第一次最小生成树
{
int u = find(edges[i].u), v = find(edges[i].v);
if(u != v)
{
f[u] = v;
e[pos[i] = ++num] = i;
}
if(num == n - 1) break;
}
int ans = 1; //等于1是因为前面做过一次了
REP(i, 0, events.size())
if(pos[events[i].olde] && !pos[events[i].newe]) //旧边在最小生成树且新边不在
{
REP(i, 0, n) f[i] = i;
int oldpos = pos[events[i].olde];
REP(j, 1, n)
if(j != oldpos) //做一次没有旧边的最小生成树
{
int u = find(edges[e[j]].u), v = find(edges[e[j]].v);
if(u != v) f[u] = v;
}
int u = find(edges[events[i].newe].u), v = find(edges[events[i].newe].v);
if(u != v) //如果做完后发现新边可以加进去,那么新边就可以代替旧边, 就可以替换
{
ans++;
pos[events[i].olde] = 0; //替换边
pos[events[i].newe] = oldpos;
e[oldpos] = events[i].newe;
}
}
return ans;
}
int main()
{
int kase = 0;
while(~scanf("%d", &n))
{
REP(i, 0, n) point[i].read();
make_edge();
make_event();
printf("Case %d: %d\n", ++kase, solve());
}
return 0;
}
紫书 例题 11-14 UVa 1279 (动点最小生成树)(详细解释)的更多相关文章
- 紫书 例题 11-13 UVa 10735(混合图的欧拉回路)(最大流)
这道题写了两个多小时-- 首先讲一下怎么建模 我们的目的是让所有点的出度等于入度 那么我们可以把点分为两部分, 一部分出度大于入度, 一部分入度大于出度 那么显然, 按照书里的思路,将边方向后,就相当 ...
- 紫书 例题8-3 UVa 1152(中途相遇法)
这道题要逆向思维, 就是求出答案的一部分, 然后反过去去寻找答案存不存在. 其实很多其他题都用了这道题目的方法, 自己以前都没有发现, 这道题专门考这个方法.这个方法可以没有一直往下求, 可以省去很多 ...
- 紫书 例题8-12 UVa 12627 (找规律 + 递归)
紫书上有很明显的笔误, 公式写错了.g(k, i)的那个公式应该加上c(k-1)而不是c(k).如果加上c(k-1)那就是这一次 所有的红气球的数目, 肯定大于最下面i行的红气球数 我用的是f的公式, ...
- 紫书 例题8-4 UVa 11134(问题分解 + 贪心)
这道题目可以把问题分解, 因为x坐标和y坐标的答案之间没有联系, 所以可以单独求两个坐标的答案 我一开始想的是按照左区间从小到大, 相同的时候从右区间从小到大排序, 然后WA 去uDebug找了数据 ...
- 紫书 例题8-17 UVa 1609 (构造法)(详细注释)
这道题用构造法, 就是自己依据题目想出一种可以得到解的方法, 没有什么规律可言, 只能根据题目本身来思考. 这道题的构造法比较复杂, 不知道刘汝佳是怎么想出来的, 我想的话肯定想不到. 具体思路紫书上 ...
- 紫书 例题 9-5 UVa 12563 ( 01背包变形)
总的来说就是价值为1,时间因物品而变,同时注意要刚好取到的01背包 (1)时间方面.按照题意,每首歌的时间最多为t + w - 1,这里要注意. 同时记得最后要加入时间为678的一首歌曲 (2)这里因 ...
- 紫书 例题 10-26 UVa 11440(欧拉函数+数论)
这里用到了一些数论知识 首先素因子都大于M等价与M! 互质 然后又因为当k与M!互质且k>M!时当且仅当k mod M! 与M!互质(欧几里得算法的原理) 又因为N>=M, 所以N!为M! ...
- 紫书 例题7-14 UVa 1602(搜索+STL+打表)
这道题想了很久不知道怎么设置状态,怎么拓展,怎么判重, 最后看了这哥们的博客 终于明白了. https://blog.csdn.net/u014800748/article/details/47400 ...
- 紫书 例题 10-2 UVa 12169 (暴力枚举)
就是暴力枚举a, b然后和题目给的数据比较就ok了. 刘汝佳这道题的讲解有点迷,书上讲有x1和a可以算出x2, 但是很明显x2 = (a * x1 +b) 没有b怎么算x2?然后我就思考了很久,最后去 ...
随机推荐
- 一些css布局
# css布局 #---bootstrap 详情请看官方文档---首先要按照相应的官方规范引入相应的css js fonts等 container相当于一个容器 一般设置一个 接下来设置行 用ro ...
- 一些html5
---匿名函数(funcation(){}())---- 一.拖拽 draggable=ture-----A拖动元素上事件 1. 拖拽开始:ondragstart2. 拖拽中:ondrag3. 拖拽结 ...
- SparkSql初级编程实践
1.Spark SQL 基本操作将下列 JSON 格式数据复制到 Linux 系统中,并保存命名为 employee.json.{ "id":1 , "name" ...
- python_三级字典
data = { "北京":{ "昌平":{ "沙河":["oldboy","test"], &qu ...
- VUE:过滤器及日期格式化moment库
VUE:过滤器及日期格式化moment库 <!DOCTYPE html> <html> <head> <meta charset="UTF-8&qu ...
- 原生JS中 callback,promise,generator,async-await 的简介
callback,promise,generator,async-await 的简介 javascript异步的发展历程. ES6 以前: 回调函数(callback):nodejs express ...
- React基础知识点全解
• propTypes.defaultProps 作为 properties 定义,也可以在组件外部通过键值对方式进行设置. • 设置组件初始的 state不支持 getIniti ...
- java深克隆与浅克隆
2015.9.19 6:45 星期五 1
- 在JAVA中将class文件编译成jar文件包,运行提示没有主清单属性
在JAVA中将class文件编译成jar文件包,运行提示没有主清单属性 Maven 项目生成jar运行时提示“没有主清单属性” 新建了一个Maven的项目,mvn compile和mvn packag ...
- PC端 java 开发蓝牙所遇到的问题
由于项目的原因.要在电脑上开发一个通过蓝牙传送数据的client.我採用的是JAVA,JSME开发. client:去搜素蓝牙信号,然后找到对应的蓝牙信号进行连接. 服务端:client须要进行连接的 ...