zoukankan      html  css  js  c++  java
  • [ZJOI 2010]base 基站选址

    Description

    题库链接

    给出 (n) 个村庄的横坐标 (D_i) 。要求在这 (n) 个村庄内最多选择 (m) 个作为通讯基站,在村庄 (i) 建造通讯基站的代价为 (C_i) 。对于村庄 (i) ,如果其左右距离超过 (S_i) 都没有通讯基站,那么需要额外的 (W_i) 的代价。求最小代价。

    (1leq nleq 20000,1leq mleq 100)

    Solution

    (f_{i,j}) 表示在第 (i) 个村庄修建第 (j) 个基站的最小费用。

    可以处理出一个数组 (cost_{k,i}) 表示第 (isim k) 个村庄之间没有被基站 (i,k) 覆盖的村庄所需的额外费用。

    那么转移方程 (f_{i,j} = minlimits_{k<i} f_{k,j-1}+cost_{k,i}+C_i) 。转移复杂度为 (O(n^2m))

    考虑优化。

    首先我们可以先把 (j) 给滚掉。其次我们注意到每次选择时都是在 (kin [1,i)) 中取一个 (f_k+cost_{k,i}) 的最小值。可以用线段树优化查询最小值。

    但剩下的就是如何处理 (cost) 的更新。

    对于一个村庄 (i) ,可以二分处理出它所能被覆盖的左右边界为 (l_i,r_i),然后在用邻接表记录 (r) 值为 (i) 的村庄有哪些,在这些村庄之后建立基站就覆盖不到 (i) 了。

    这样当我们从 (i) 推到 (i + 1) 时,对于所有 (r_k=i) 的村庄若从村庄 (1sim l_k-1) 转移过来则必定要赔偿村庄 (k) 的费用,这样就可以用线段树区间加,即在区间 ([1,l_k)) 加上村庄 (k) 的额外费用。

    总复杂度为(O(nmlog n))

    Code

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 20000, inf = ~0u>>1;
    
    int n, m, d[N+5], c[N+5], s[N+5], w[N+5], l[N+5], r[N+5]; long long f[N+5];
    vector<int>to[N+5];
    struct Segment_tree {
        #define lr(o) (o<<1)
        #define rr(o) (o<<1|1)
        long long sgm[(N<<2)+5], lazy[(N<<2)+5];
        void build(int o, int l, int r) {
            lazy[o] = 0; int mid = (l+r)>>1;
            if (l == r) {sgm[o] = f[l]; return; }
            build(lr(o), l, mid); build(rr(o), mid+1, r);
            sgm[o] = min(sgm[lr(o)], sgm[rr(o)]);
        }
        void pushdown(int o) {
            sgm[lr(o)] += lazy[o], lazy[lr(o)] += lazy[o];
            sgm[rr(o)] += lazy[o], lazy[rr(o)] += lazy[o];
            lazy[o] = 0;
        }
        void update(int o, int l, int r, int a, int b, long long k) {
            if (a <= l && r <= b) {sgm[o] += k, lazy[o] += k; return; }
            if (lazy[o]) pushdown(o); int mid = (l+r)>>1;
            if (a <= mid) update(lr(o), l, mid, a, b, k);
            if (b > mid) update(rr(o), mid+1, r, a, b, k);
            sgm[o] = min(sgm[lr(o)], sgm[rr(o)]);
        }
        long long query(int o, int l, int r, int a, int b) {
            if (a <= l && r <= b) return sgm[o];
            if (lazy[o]) pushdown(o); int mid = (l+r)>>1;
            long long c1 = inf, c2 = inf;
            if (a <= mid) c1 = query(lr(o), l, mid, a, b);
            if (b > mid) c2 = query(rr(o), mid+1, r, a, b);
            return min(c1, c2);
        }
    }T;
    
    void work() {
        scanf("%d%d", &n, &m);
        for (int i = 2; i <= n; i++) scanf("%d", &d[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &s[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
        ++m; ++n; d[n] = w[n] = inf;
        for (int i = 1; i <= n; i++) {
            r[i] = lower_bound(d+1, d+n+1, d[i]+s[i])-d;
            l[i] = lower_bound(d+1, d+n+1, d[i]-s[i])-d;
            if (d[r[i]]-d[i] > s[i]) --r[i];
            to[r[i]].push_back(i);
        }
        long long tol = 0;
        for (int i = 1; i <= n; i++) {
            f[i] = c[i]+tol;
            for (int j = 0, sz = to[i].size(); j < sz; j++)
                tol += w[to[i][j]];
        }
        long long ans = f[n];
        for (int i = 2; i <= m; i++) {
            T.build(1, 1, n);
            for (int j = 1; j <= n; j++) {
                f[j] = (j != 1 ? T.query(1, 1, n, 1, j-1) : 0)+c[j];
                for (int k = 0, sz = to[j].size(); k < sz; k++)
                    if (l[to[j][k]] != 1) T.update(1, 1, n, 1, l[to[j][k]]-1, w[to[j][k]]);
            }
            ans = min(ans, f[n]);
        }
        printf("%lld
    ", ans);
    }
    int main() {work(); return 0; }
  • 相关阅读:
    反恐24小时[第1季]——我打赌这是我第一次写观后感
    单片机设计-带时间显示的十字路口交通灯控制系统
    ASPxGridView控件的使用
    DataRow复制一行到另一个DataTable
    参数化、检查点、关联基本理解
    录制方式及一些脚本字段信息
    参数化的一些实践
    [转载]WinXp 自动运行的设置方法
    [转载]Windows Xp中如何设置自动登录
    [转载]WinXp 自动运行的设置方法
  • 原文地址:https://www.cnblogs.com/NaVi-Awson/p/8678010.html
Copyright © 2011-2022 走看看