P4047 [JSOI2010] 部落划分
解题思路
问题分析
-
问题描述:将n个居住点划分为k个部落,使得不同部落间的最小距离最大化
-
关键概念:
-
部落距离:两个部落中最近的两个居住点的距离
-
目标:在所有可能的划分中,找出使最近部落距离最大的划分方案
-
算法选择
-
逆向思维:将问题转化为图论问题
-
初始状态:每个点是一个独立部落(n个部落)
-
合并过程:不断合并最近的部落,直到剩下k个部落
-
答案:最后一次合并前的边长度
-
-
Kruskal算法变形:
-
与最小生成树类似,但停止条件不同
-
当连通分量(部落)数量减至k时停止
-
实现步骤
-
预处理:
-
计算所有点对之间的距离(构建完全图)
-
初始化并查集(每个点自成一个部落)
-
-
核心算法:
-
按边长度升序排序
-
依次处理每条边,合并不同部落的点
-
当部落数减至k时,当前边长度即为答案
-
-
正确性证明:
-
通过合并最近的点,确保被合并的部落间距离尽可能小
-
剩下的部落间距离(即下一条边的长度)就是最小可能的最大值
-
复杂度分析
-
计算所有边:O(n²)
-
排序:O(n² log n)(主要瓶颈)
-
Kruskal:O(n² α(n))(α为反阿克曼函数)
-
总体:O(n² log n),对于n≤1000完全可行
关键点说明
-
停止条件:部落数减至k时立即停止
-
答案确定:停止时的下一条边长度即为所求
-
并查集作用:高效管理部落合并过程
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 10; // 定义边的结构体:x和y是顶点编号,z是两点间距离 struct node { int x, y; double z; }; node t[N]; // 存储所有可能的边 int f[N]; // 并查集数组 int n, k, cnt; // n:居住点数, k:部落数, cnt:边数 int x[N], y[N]; // 存储每个居住点的坐标 // 计算两点间欧几里得距离 double dis(int i, int j) { return sqrt((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j])); } // 并查集查找函数(带路径压缩) int find(int x) { if(f[x] != x) f[x] = find(f[x]); return f[x]; } // 并查集合并函数 void merge(int x, int y) { int fx = find(x), fy = find(y); f[fy] = fx; } // 边按距离从小到大排序的比较函数 bool cmp(node a, node b) { return a.z < b.z; } // Kruskal算法变形实现 void kruskal() { int sum = n; // 初始时每个点自成一个部落 // 遍历所有边(已按距离排序) for(int i = 1; i <= cnt; i++) { int u = t[i].x, v = t[i].y; // 如果两点不在同一部落 if(find(u) != find(v)) { merge(u, v); // 合并部落 sum--; // 部落总数减少 // 当部落数减到k时,当前边长度就是答案 if(sum == k - 1) { printf("%.2f", t[i].z); break; } } } } int main() { cin >> n >> k; // 初始化并查集和读取坐标 for(int i = 1; i <= n; i++) { cin >> x[i] >> y[i]; f[i] = i; // 初始时每个点自成一个部落 } // 生成所有可能的边(完全图) for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { t[++cnt] = {i, j, dis(i,j)}; // 存储边信息 } } // 按边长度升序排序 sort(t + 1, t + 1 + cnt, cmp); // 执行变形Kruskal算法 kruskal(); return 0; }

浙公网安备 33010602011771号