uva1279星际游击队——动点最小生成树

本文探讨了在多个匀速动点的情况下,如何计算最小生成树(MST)随时间变化的次数。通过预计算静态MST,跟踪可能引起MST变动的事件,并按时间排序处理这些事件,实现了动态MST的高效更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

有n个匀速动点,求最小生成树的改变次数

思路:

。。。没有思路
看题解,应该算是一道思维题。
动态问题的一般做法是先求出一个静态的解,然后求出解可能发生改变的事件,事件按照时间排序,依次处理。
首先什么时候最小生成树(MST)会变化呢?
先求出最开始的最小生成树(MST),当MST中的某条线段v长度被不在MST的线段u取代的时候,最小生成树才会发生变化,切换一定是树内一条边和树外一条边,构成了新的MST。
设一条在MST的边e1,和一条不在MST的边e2
容易想到,当w(e1) = w(e2) 的时候,才可能发生切换。为什么是可能呢,有两点原因:

  1. 在这一时刻t, w(e1) = w(e2) , 过了这一时刻,w(e1) < w(e2),这时候所以保持原状就挺好
  2. e2 虽然在MST的外部,但是切换会导致MST内出现环,还会少一个点。

所以可以看到,只用考虑那些 t 时刻过后权值比e1小的边,而且在原MST将旧边删除后,新边e2要能与其构成新的MST。

#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 50+5;
const int maxEdges = maxn*(maxn+1)/2;
const double eps = 1e-8;

int n , nke; // 边的个数
int pa[maxn];
// 点 
struct Point{
	double x, y, z;	// 初始坐标 
	double dx, dy, dz; // 位移矢量 
	void read() {
	    scanf("%lf%lf%lf%lf%lf%lf", &x, &y, &z, &dx, &dy, &dz);
	}
}kp[maxn];
// 边
struct Edge{
	double a, b, c; // distance(u, v)的平方 = a*t^2 + b*t + c
	int u, v;	// u -> v 
	bool operator < (const Edge& rhs) const { // 比较初始的距离,也就是t = 0时, distance = c; 
	    return c - rhs.c < 0;
	}
}ke[maxEdges];
// 事件 
struct Event{
	double t;
	int olde, newe; // 该事件过后,新边将比旧边更小
	Event(double a, int b, int c):t(a),newe(b),olde(c){} 
	bool operator < (const Event& rhs) const{
		return t - rhs.t < 0;
	}
}; 
vector<Event> events;
 
inline double sqr(double x) { return x*x; }
void init_findset() { for(int i = 0; i < n; i++) pa[i] = i; }
int findset(int x) { return pa[x] == x ? x : pa[x] = findset(pa[x]); }

// 求每两点之间的距离 
void make_edge(){
	nke = 0;
	for(int i = 0; i < n; ++i){
		for(int j = i+1; j < n; ++j){
			ke[nke].a = sqr(kp[i].dx-kp[j].dx) + sqr(kp[i].dy-kp[j].dy) + sqr(kp[i].dz-kp[j].dz);
			ke[nke].b = 2*((kp[i].dx-kp[j].dx)*(kp[i].x-kp[j].x) + (kp[i].dy-kp[j].dy)*(kp[i].y-kp[j].y) + (kp[i].dz-kp[j].dz)*(kp[i].z-kp[j].z));
			ke[nke].c = sqr(kp[i].x-kp[j].x) + sqr(kp[i].y-kp[j].y) + sqr(kp[i].z-kp[j].z);
			ke[nke].u = i;
			ke[nke].v = j;
			++nke;
		}
	}
	sort(ke, ke + nke);
}

// 计算每两条边相等的时间,生成事件 
void make_event(){
	events.clear();
	for(int i = 0; i < nke; ++i){
		for(int j = i+1; j < nke; ++j){
			int s1 = i, s2 = j;
			if(ke[s1].a - ke[s2].a < 0) s1 = j, s2 = i;	// s1是二次型系数大的那个,函数图像更陡峭一点 
			// 令两个方程相等,同类型相减,计算与x轴的交点 
			double a = ke[s1].a - ke[s2].a;
			double b = ke[s1].b - ke[s2].b;
			double c = ke[s1].c - ke[s2].c;
			if(fabs(a) < eps){ // bt + c = 0
				if (fabs(b) < eps) continue;	// 无解
				if (b > 0) { swap(s1, s2); b = -b; c = -c; }
				if (c > 0) { events.push_back(Event(-c / b, s1, s2)); }
				continue;
			}
			double delta = b*b - 4*a*c;
			if(fabs(delta) < eps) continue;  // 判别式<0, 无解 
			delta = sqrt(delta);
			double t1 = -(b + delta)/(2*a);
			double t2 = (delta - b)/(2*a);
			if(t1 > 0) events.push_back(Event(t1, s1, s2));
			if(t2 > 0) events.push_back(Event(t2, s2, s1));
		}
	}
	sort(events.begin(), events.end());
}

int solve(){
	int pos[maxEdges]; // pos[i]: 第i条边在最小生成树(MST)中的编号,0代表不在MST中 
    int e[maxn];    // e[i]: MST中的第 i 条边 
    
    // 初始化 
	for(int i = 0; i < nke; ++i) pos[i] = 0;
	init_findset();
	
	// 初始的MST
	int idx = 0;
	for(int i = 0; i < nke; ++i){
		int u = findset(ke[i].u), v = findset(ke[i].v);
		if(u != v){
			pos[i] = ++idx;
			e[pos[i]] = i;
			pa[u] = v;
		}
		if(idx == n-1) break;
	} 
	
	int ans = 1;
	for(int i = 0; i < events.size(); ++i){
		if(pos[events[i].olde]&&0 == pos[events[i].newe]){ // 旧边在MST里,新边不在,满足更新MST的条件
			// 把 MST 中除了旧边的部分都加入并查集 
			int oldedge = pos[events[i].olde];
			init_findset();
			for(int j = 1; j < n; ++j){
			 	if(j != oldedge){
			 		int u = findset(ke[e[j]].u), v = findset(ke[e[j]].v);
			 		if(u != v) pa[u] = v;
				}
			}
			int u = findset(ke[events[i].newe].u), v = findset(ke[events[i].newe].v);
			if(u != v){	// 找到新的MST 
				++ans;
				pos[events[i].newe] = oldedge; 	// 把旧边在MST中的编号赋给新边 
				e[oldedge] = events[i].newe;	//更新MST中新边的对应原编号 
				pos[events[i].olde] = 0;	// 从MST中删除旧边 
			}
		}
	}
	return ans;
}

int main()
{
	freopen("in.txt","r",stdin);
	int kase = 1;
	while(scanf("%d",&n) == 1&&n){
		for(int i = 0; i < n; ++i) kp[i].read();
		make_edge();
		make_event();
		int ans = solve();
		printf("Case %d: %d\n", kase++, ans);
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值