#P3715. 第2题-最大化城市CDN节点建设的最小服务质量
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 652
            Accepted: 102
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2025年9月17日-非AI方向(通软&嵌软&测试&算法&数据科学)
                              
                      
          
 
- 
                        算法标签>二分算法          
 
第2题-最大化城市CDN节点建设的最小服务质量
题解思路
对于长度为 n 的城市数组 cities,每座城市初始拥有若干 CDN 机房。每个机房的服务半径为 r,相当于它能为相距不超过 r 的城市提供服务。给定还可以新建 k 个机房,允许在同一城市重复建设,目标是使所有城市的最小“服务质量”(即能访问到的机房总数)最大化。
我们可以抽象为:让每个城市 i “窗口”内([i–r, i+r])的机房总和至少达到 m,问是否能用不超过 k 次增量操作做到这一点。增量操作:在某个城市 j 新增一台机房,会使得所有覆盖到 j 的城市窗口内总和加 1。
- 二分答案:对最小服务质量 m 进行二分搜索。
 - 可行性检验:固定候选值 m,判断是否在 k 次新增内使所有城市窗口和 ≥ m。
 - 贪心策略:从城市 0 到 n–1 遍历,维护当前对每个城市由新增机房带来的累计增量 
cur_add(通过差分数组模拟区间加法)。若某城市窗口和initial[i] + cur_add小于 m,则在最右能覆盖该城市的点j = min(n–1, i+r)上新增所需的机房数need = m – (initial[i] + cur_add),并将need加到差分数组上。若累计新增量超出 k,则判定不可行。 
这样,每次检验的时间复杂度为 O(n),二分需要 O(log T) 次,其中 T 约为初始最小窗口和 + k。
算法流程
- 
前缀和
计算城市机房数的前缀和ps,用以快速求任意区间和。 - 
初始窗口和
对每座城市 i,计算其初始服务质量initial[i] = sum(cities[max(0, i-r) .. min(n-1, i+r)]) = ps[min(n-1,i+r)] - ps[max(0,i-r)-1] - 
二分答案
lo = 0,hi = min(initial) + k- 循环直至 
lo < hi:设mid = (lo + hi + 1) // 2- 若 
check(mid)可行,则lo = mid - 否则 
hi = mid - 1 
 - 若 
 
 - 
可行性检验
check(m)- 设差分数组 
diff[n]全 0,cur_add = 0,used = 0 - 对 i 从 0 遍历到 n-1:
cur_add += diff[i]- 计算当前覆盖:
cover = initial[i] + cur_add - 若 
cover < m,需新增need = m - cover:used += need,若used > k返回 false- 选点 
j = min(n-1, i+r),其增量区间[L = max(0,j-r), R = min(n-1,j+r)],而此时L=i,故:cur_add += need if R+1 < n: diff[R+1] -= need 
 
 - 若遍历结束仍未超出 k,则返回 true。
 
 - 设差分数组 
 - 
返回 最终二分出的
lo。 
复杂度分析
- 计算初始前缀和和窗口和:O(n)
 - 每次可行性检验:O(n)
 - 二分次数:O(log(初始最小窗口和 + k)),该值上限约 O(log(n·max(cities)+k))
 - 总复杂度:O(n log (n·max(cities)+k)),对 n ≤ 10^5 完全可行。
 
代码实现
Python 实现
def max_min_service(cities, r, k):
    n = len(cities)
    # 1. 前缀和
    ps = [0] * n
    ps[0] = cities[0]
    for i in range(1, n):
        ps[i] = ps[i-1] + cities[i]
    # 2. 初始窗口和
    initial = [0] * n
    for i in range(n):
        L = max(0, i - r)
        R = min(n - 1, i + r)
        initial[i] = ps[R] - (ps[L - 1] if L > 0 else 0)
    # 二分上下界
    lo, hi = 0, min(initial) + k
    def check(m):
        diff = [0] * (n + 1)  # 差分数组,多一位便于操作
        cur_add = used = 0
        for i in range(n):
            cur_add += diff[i]
            cover = initial[i] + cur_add
            if cover < m:
                need = m - cover
                used += need
                if used > k:
                    return False
                # 在 j 位置新增 need 台机房
                j = min(n - 1, i + r)
                R = min(n - 1, j + r)
                # 新增在 i 开始生效,到 R 结束
                cur_add += need
                diff[R + 1] -= need
        return True
    # 二分寻找最大可行 m
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if check(mid):
            lo = mid
        else:
            hi = mid - 1
    return lo
# 读入输出
if __name__ == "__main__":
    import sys
    data = sys.stdin.read().split()
    r, k, n = map(int, data[:3])
    cities = list(map(int, data[3:]))
    print(max_min_service(cities, r, k))
Java 实现
import java.io.*;
import java.util.*;
public class Main {
    public static long maxMinService(int[] cities, int r, long k) {
        int n = cities.length;
        // 1. 前缀和
        long[] ps = new long[n];
        ps[0] = cities[0];
        for (int i = 1; i < n; i++) {
            ps[i] = ps[i-1] + cities[i];
        }
        // 2. 初始窗口和
        long[] initial = new long[n];
        for (int i = 0; i < n; i++) {
            int L = Math.max(0, i - r);
            int R = Math.min(n - 1, i + r);
            initial[i] = ps[R] - (L > 0 ? ps[L-1] : 0);
        }
        long lo = 0, hi = Arrays.stream(initial).min().getAsLong() + k;
        // 检验函数
        class Checker {
            boolean check(long m) {
                long[] diff = new long[n + 1];
                long curAdd = 0, used = 0;
                for (int i = 0; i < n; i++) {
                    curAdd += diff[i];
                    long cover = initial[i] + curAdd;
                    if (cover < m) {
                        long need = m - cover;
                        used += need;
                        if (used > k) return false;
                        int j = Math.min(n - 1, i + r);
                        int R = Math.min(n - 1, j + r);
                        curAdd += need;
                        diff[R + 1] -= need;
                    }
                }
                return true;
            }
        }
        Checker checker = new Checker();
        while (lo < hi) {
            long mid = (lo + hi + 1) >>> 1;
            if (checker.check(mid)) lo = mid;
            else hi = mid - 1;
        }
        return lo;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int r = Integer.parseInt(br.readLine());
        long k = Long.parseLong(br.readLine());
        int n = Integer.parseInt(br.readLine());
        String[] parts = br.readLine().split(" ");
        int[] cities = new int[n];
        for (int i = 0; i < n; i++) cities[i] = Integer.parseInt(parts[i]);
        System.out.println(maxMinService(cities, r, k));
    }
}
C++ 实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll maxMinService(const vector<int>& cities, int r, ll k) {
    int n = cities.size();
    // 1. 前缀和
    vector<ll> ps(n);
    ps[0] = cities[0];
    for (int i = 1; i < n; i++) {
        ps[i] = ps[i-1] + cities[i];
    }
    // 2. 初始窗口和
    vector<ll> initial(n);
    for (int i = 0; i < n; i++) {
        int L = max(0, i - r);
        int R = min(n - 1, i + r);
        initial[i] = ps[R] - (L > 0 ? ps[L-1] : 0);
    }
    ll lo = 0, hi = *min_element(initial.begin(), initial.end()) + k;
    // 检验函数
    auto check = [&](ll m) {
        vector<ll> diff(n+1, 0);
        ll curAdd = 0, used = 0;
        for (int i = 0; i < n; i++) {
            curAdd += diff[i];
            ll cover = initial[i] + curAdd;
            if (cover < m) {
                ll need = m - cover;
                used += need;
                if (used > k) return false;
                int j = min(n - 1, i + r);
                int R = min(n - 1, j + r);
                curAdd += need;
                diff[R+1] -= need;
            }
        }
        return true;
    };
    // 二分
    while (lo < hi) {
        ll mid = (lo + hi + 1) >> 1;
        if (check(mid)) lo = mid;
        else hi = mid - 1;
    }
    return lo;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int r; ll k; int n;
    cin >> r >> k >> n;
    vector<int> cities(n);
    for (int i = 0; i < n; i++) {
        cin >> cities[i];
    }
    cout << maxMinService(cities, r, k) << "\n";
    return 0;
}
        题目内容
CDN(content delivery network,内容分发网络)机房可以加速网站内容的加载速度,通过在地理位置上靠近用户的地点存储网站的内容副本。
给定一个下标从 0 开始长度为 n 的整数数组 cities ,其中 cities 表示第 i 座 城市中现有的 CDN 机房数量。
每个 CDN 机房能够服务的城市覆盖范围由其所在位置决定,所有节点具有相同的城市盖范围。
如果给定的覆盖范围是 r ,则位于城市 i 的 CDN 机房可以为其周围 ∣i−j∣<=r 范围内的所有城市提供服务,这里 ∣x∣ 表示 x 的绝对值。例如,∣5−3∣=2,∣4−9∣=5 。
一座城市的“服务质量”定义为能够访问到的 CDN 机房的数量,你的任务是在全国范围内选择多个城市来新建 k 个 CDN 机房(同一个城市可重复建设),以确保即使在网络流量高峰时期也能提供最佳的服务质量。
现在给定两个整数 r 和 k ,你需要决定在哪些城市新建 k 个 CDN 机房,使所有城市中最小的服务质量达到最大,并返回最小服务质量。
输入描述
第一行数字为 r ,第二行数字为 k ,第三行数字为城市数量 n ,第四行 n 个数字为 cities 的值
输入限制:
1<=n<=100000
0<=cities[i]<=100000
0<=r<=n−1
0<=k<=109
输出描述
返回所有城市中最小服务质量达到最大时的 CDN 节点数
样例1
输入
1
2
5
1 2 4 9 3
输出
5
说明
输入转换成 cities=[1,2,4,9,3],r=1,k=2
最优方案之一是把 2 个节点都建在城市 1 ,建设完后每座城市的CDN节点数目分别为 [1,4,4,9,3] 。
- 
城市 0 的节点数目为 1+4=5。
 - 
城市 1 的节点数目为 1+4+4=9 。
 - 
城市 2 的节点数目为 4+4+9=17 。
 - 
城市 3 的节点数目为 4+9+3=16 。
 - 
城市 4 的节点数目为 9+3=12 。
 
这些城市中节点数最小的是 5 ,已无法使得这个最小值更大。
样例2
输入
0
3
4
5 5 5 5
输出
5
说明
输入转换成 cities=[5,5,5,5],r=0,k=3。无论如何安排,总有一座城市的 CDN 节点数目是 5 ,所以最优解是 5 。