P4047 [JSOI2010] 部落划分

解题思路

问题分析

  1. 问题描述:将n个居住点划分为k个部落,使得不同部落间的最小距离最大化

  2. 关键概念

    • 部落距离:两个部落中最近的两个居住点的距离

    • 目标:在所有可能的划分中,找出使最近部落距离最大的划分方案

算法选择

  1. 逆向思维:将问题转化为图论问题

    • 初始状态:每个点是一个独立部落(n个部落)

    • 合并过程:不断合并最近的部落,直到剩下k个部落

    • 答案:最后一次合并前的边长度

  2. Kruskal算法变形

    • 与最小生成树类似,但停止条件不同

    • 当连通分量(部落)数量减至k时停止

实现步骤

  1. 预处理

    • 计算所有点对之间的距离(构建完全图)

    • 初始化并查集(每个点自成一个部落)

  2. 核心算法

    • 按边长度升序排序

    • 依次处理每条边,合并不同部落的点

    • 当部落数减至k时,当前边长度即为答案

  3. 正确性证明

    • 通过合并最近的点,确保被合并的部落间距离尽可能小

    • 剩下的部落间距离(即下一条边的长度)就是最小可能的最大值

复杂度分析

  1. 计算所有边:O(n²)

  2. 排序:O(n² log n)(主要瓶颈)

  3. Kruskal:O(n² α(n))(α为反阿克曼函数)

  4. 总体:O(n² log n),对于n≤1000完全可行

关键点说明

  1. 停止条件:部落数减至k时立即停止

  2. 答案确定:停止时的下一条边长度即为所求

  3. 并查集作用:高效管理部落合并过程

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

 

posted @ 2025-04-28 21:53  CRt0729  阅读(29)  评论(0)    收藏  举报