题意:
有n个匀速动点,求最小生成树的改变次数
思路:
。。。没有思路
看题解,应该算是一道思维题。
动态问题的一般做法是先求出一个静态的解,然后求出解可能发生改变的事件,事件按照时间排序,依次处理。
首先什么时候最小生成树(MST)会变化呢?
先求出最开始的最小生成树(MST),当MST中的某条线段v长度被不在MST的线段u取代的时候,最小生成树才会发生变化,切换一定是树内一条边和树外一条边,构成了新的MST。
设一条在MST的边e1,和一条不在MST的边e2
容易想到,当w(e1) = w(e2) 的时候,才可能发生切换。为什么是可能呢,有两点原因:
- 在这一时刻t, w(e1) = w(e2) , 过了这一时刻,w(e1) < w(e2),这时候所以保持原状就挺好
- 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;
}