树中点对距离

题目大意

在一颗N个结点的树上,统计有多少点对最短距离<=m。(点对不存在顺序性)N<=10000

点分治

我们选取一个点x作根,那么任何点对都分成两种类型
1、经过x
2、不经过x
我们对经过x的进行统计,对于不经过x的继续在x的子树中分治下去。这就是点分治。
我们处理出每个点的深度,排序后就很容易统计经过x的个数。
不过有可能出现一对点对的lca是y而不是x,然后他们被统计进去了,要在往下分治的过程中减去他们。
为了使分治树不退化,每次选取重心充当根,那么最多log n层,每一层点总和不超过n,复杂度为n log n。

参考程序

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=10000+10;
int d[maxn],s[maxn],h[maxn],go[maxn*2],next[maxn*2],dis[maxn*2],a[maxn];
bool bz[maxn];
int i,j,k,l,t,n,m,tot,ans,top;
void add(int x,int y,int z){
    go[++tot]=y;
    dis[tot]=z;
    next[tot]=h[x];
    h[x]=tot;
}
void dfs(int x,int y){
    a[++top]=x;
    s[x]=1;
    int t=h[x];
    while (t){
        if (go[t]!=y&&!bz[go[t]]){
            dfs(go[t],x);
            s[x]+=s[go[t]];
        }
        t=next[t];
    }
}
void dg(int x,int y){
    int t=h[x];
    while (t){
        if (go[t]!=y&&!bz[go[t]]){
            d[go[t]]=d[x]+dis[t];
            dg(go[t],x);
        }
        t=next[t];
    }
}
bool cmp(int x,int y){
    return d[x]<d[y];
}
void solve(int x){
    int i,j,k,t;
    top=0;
    dfs(x,0);
    if (ans){
        sort(a+1,a+top+1,cmp);
        i=0;
        fd(j,top,1){
            while (i<j-1){
                if (d[a[i+1]]+d[a[j]]<=m) i++;else break;
            }
            if (i==j) i--;
            ans-=i;
        }
    }
    j=x;k=0;
    while (1){
        t=h[j];
        while (t){
            if (!bz[go[t]]&&go[t]!=k&&s[go[t]]>s[x]/2){
                k=j;
                j=go[t];
                break;
            }
            t=next[t];
        }
        if (!t) break;
    }
    d[j]=0;
    dg(j,0);
    sort(a+1,a+top+1,cmp);
    i=0;k=j;
    fd(j,top,1){
            while (i<j-1){
                if (d[a[i+1]]+d[a[j]]<=m) i++;else break;
            }
            if (i==j) i--;
            ans+=i;
        }
    t=h[k];
    bz[k]=1;
    while (t){
        if (!bz[go[t]]) solve(go[t]);
        t=next[t];
    }
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,n-1){
        scanf("%d%d%d",&j,&k,&l);
        add(j,k,l);add(k,j,l);
    }
    solve(1);
    printf("%d\n",ans);
}
<think>我们正在解决的是“最佳植距离”问题,这是一个典型的二分查找应用。 问题描述:在一条直线上有若干个坑位(位置已给定),我们需要在这些坑位中种植苗,要求种植的苗数量为指定的target棵,并且使得相邻苗之间的最小间距尽可能大。 注意:苗必须种在给定的坑位位置上。 思路: 1. 对坑位位置进行排序(因为坑位位置不一定有序)。 2. 最小间距的取值范围:最小可能是1(但实际取决于坑位间距),最大可能是坑位序列中首尾的距离(即全部坑位范围)。 3. 使用二分查找在可能的间距范围内寻找最大的最小间距。对于每个候选间距mid,检查是否可以在坑位中种植至少target棵,且任意两棵之间的间距都不小于mid。 4. 检查函数(check函数)的设计: - 首先种下第一棵(在第一个坑位位置) - 然后遍历后续坑位,当遇到一个坑位距离上一棵距离大于等于mid时,就在此坑位种下一棵,并记录种的位置和数量。 - 如果最终种下的的数量大于等于target,说明当前间距mid可行,可以尝试更大的间距(即left=mid+1),否则说明当前间距太大,需要减小间距(即right=mid-1)。 5. 注意:二分查找的边界条件,以及最终答案的确定。 根据引用[2]和引用[3],我们采用二分查找方法。 实现步骤: 1. 排序坑位数组。 2. 确定二分查找的左右边界:left=1, right=holes[n-1]-holes[0] (n为坑位数量) 3. 在[left, right]区间进行二分,对于每个mid,检查是否可行。 4. 在检查函数中,使用贪心策略:从第一个坑位开始种,然后尽可能选择距离上一棵大于等于mid的坑位。 注意:题目要求的是最大的最小间距,因此我们使用二分查找来最大化这个最小间距。 下面我们用C++实现这个算法。 </think># 最佳植距离算法实现(C++) 最佳植距离问题是一个典型的**最大化最小值问题**,可以通过二分查找高效解决。以下是完整的C++实现: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 检查在给定间距下能否种植足够数量的苗 bool canPlantTrees(vector<int>& holes, int target, int minDistance) { int count = 1; // 第一棵种在第一个坑位 int lastPosition = holes[0]; for (int i = 1; i < holes.size(); i++) { // 如果当前位置与上一棵距离≥最小间距,可以种植 if (holes[i] - lastPosition >= minDistance) { count++; lastPosition = holes[i]; if (count >= target) return true; } } return count >= target; } // 计算最大可保证的最小间距 int minSpace(vector<int>& holes, int target) { if (holes.empty() || target <= 0 || target > holes.size()) return -1; sort(holes.begin(), holes.end()); int left = 1; // 最小可能间距 int right = holes.back() - holes.front(); // 最大可能间距 int result = -1; while (left <= right) { int mid = left + (right - left) / 2; if (canPlantTrees(holes, target, mid)) { result = mid; // 当前间距可行,尝试更大的间距 left = mid + 1; } else { right = mid - 1; // 当前间距不可行,尝试更小的间距 } } return result; } int main() { // 示例测试 vector<int> holes = {1, 3, 7, 9, 13}; int target = 3; int maxMinDistance = minSpace(holes, target); if (maxMinDistance != -1) { cout << "最大可保证的最小间距为:" << maxMinDistance << endl; } else { cout << "无法种植指定数量的苗" << endl; } return 0; } ``` ## 算法解析 ### 1. 问题模型 给定$n$个坑位位置$holes = \{pos_1, pos_2, \dots, pos_n\}$,需在其中种植$target$棵苗,使得所有相邻苗间距的最小值最大化: $$\text{最大化} \min_{(i<j)} \{ |pos_i - pos_j| \} \quad \text{且} \quad i,j \text{为实际种植位置}$$ ### 2. 算法步骤 1. **排序坑位位置**:$O(n \log n)$ ```cpp sort(holes.begin(), holes.end()); ``` 2. **二分查找框架**:$O(\log(\text{最大间距}))$ - 左边界$left = 1$(最小间距) - 右边界$right = holes[n-1] - holes[0]$(最大间距) - 检查中点$mid = \frac{left + right}{2}$ 3. **验证函数**:$O(n)$ ```cpp bool canPlantTrees(vector<int>& holes, int target, int minDistance) ``` - 贪心策略:尽可能在满足最小间距的坑位种植苗 - 第一棵固定在第一个坑位 - 后续苗需满足$holes[i] - lastPosition \geq minDistance$ ### 3. 复杂度分析 | 步骤 | 时间复杂度 | |--------------------|-------------------| | 排序坑位 | $O(n \log n)$ | | 二分查找 | $O(\log D)$ | | 验证函数(每次) | $O(n)$ | | **总复杂度** | $O(n \log D)$ | 其中: - $n$:坑位数量 - $D$:坑位最大跨度$(holes[n-1]-holes[0])$ ## 测试用例 ```cpp void test() { // 基础测试 vector<int> holes1 = {1, 3, 7, 9, 13}; cout << minSpace(holes1, 3) << endl; // 输出: 6 (位置:1,7,13) // 边界测试 vector<int> holes2 = {5}; cout << minSpace(holes2, 1) << endl; // 输出: 0 (单棵) // 无法满足的情况 vector<int> holes3 = {2, 4, 6}; cout << minSpace(holes3, 4) << endl; // 输出: -1 (数量不足) // 密集坑位测试 vector<int> holes4 = {1,2,3,4,5,6,7,8,9,10}; cout << minSpace(holes4, 4) << endl; // 输出: 3 (位置:1,4,7,10) } ``` ## 数学证明 ### 二分查找可行性 设最优解为$d^*$,则满足: 1. $\forall d > d^*$:无法种植$target$棵苗 2. $\forall d \leq d^*$:可种植$target$棵苗 ### 贪心策略正确性 定理:在有序坑位序列中,从最小位置开始贪心选择满足最小间距的坑位,得到的种植方案是最优的。 证明:设存在更优方案$\{p_1, p_2, \dots, p_k\}$满足最小间距更大,则: - 至少存在一个坑位$p_i$满足$p_i < \text{贪心选择的对应位置}$ - 但贪心策略会选择第一个满足条件的位置,矛盾 ## 应用场景 1. 风沙防护林间距规划 2. 无线基站最优部署 3. 农业作物最佳种植密度 4. 城市设施布局优化 ## 相关问题 1. 如何在$O(n)$时间内解决特殊分布的坑位问题? 2. 如果允许在非坑位位置种植,算法如何调整? 3. 如何处理多维空间中的最优布局问题? 4. 当苗有不同种类要求时,算法如何扩展? 5. 如何优化二分查找中的边界条件以减少迭代次数? > **参考** > 二分查找解决最大化最小值问题是一种经典算法范式,在资源调度、网络优化等领域有广泛应用[^1][^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值