• 洛谷P2605 基站选址


    神TM毒瘤线段树优化DP......新姿势get。

    题意:有n个村庄,在里面选不多于k个建立基站。

    建立基站要ci的费用。如果一个村庄方圆si内没有基站,那么又要支出wi的费用。求最小费用。

    解:很显然想到DP,f[i][j]表示前i个村庄里面放了j个基站,其中第i个一定选的最小费用。费用只统计不超过i的。

    转移就是枚举从p转移,对于p到i的每一个,检查是否需要付钱。

    这样是n³k的,只有20分。

     1 #include <cstdio>
     2 #include <algorithm>
     3 
     4 typedef long long LL;
     5 const int N = 20010;
     6 const LL INF = 0x3f3f3f3f3f3f3f3f;
     7 
     8 LL d[N], s[N], f[N][210], c[N], w[N];
     9 int n, first[N], last[N];
    10 
    11 inline LL val(int l, int r) {
    12     LL ans = 0;
    13     for(int i = l + 1; i < r; i++) {
    14         if(l < first[i] && r > last[i]) {
    15             ans += w[i];
    16         }
    17     }
    18     return ans;
    19 }
    20 
    21 int main() {
    22     int k;
    23     LL ans = 0;
    24     scanf("%d%d", &n, &k);
    25     for(int i = 2; i <= n; i++) {
    26         scanf("%lld", &d[i]);
    27     }
    28     for(int i = 1; i <= n; i++) {
    29         scanf("%lld", &c[i]);
    30     }
    31     for(int i = 1; i <= n; i++) {
    32         scanf("%lld", &s[i]);
    33     }
    34     for(int i = 1; i <= n; i++) {
    35         scanf("%lld", &w[i]);
    36         ans += w[i];
    37         f[i][0] = f[i - 1][0] + w[i];
    38     }
    39 
    40     for(int i = 1; i <= n; i++) { // prework
    41         int l = 1, r = i;
    42         while(l < r) {
    43             int mid = (l + r) >> 1;
    44             if(d[mid] >= d[i] - s[i]) {
    45                 r = mid;
    46             }
    47             else {
    48                 l = mid + 1;
    49             }
    50         }
    51         first[i] = r;
    52         l = i;
    53         r = n;
    54         while(l < r) {
    55             int mid = (l + r + 1) >> 1;
    56             if(d[mid] <= d[i] + s[i]) {
    57                 l = mid;
    58             }
    59             else {
    60                 r = mid - 1;
    61             }
    62         }
    63         last[i] = r;
    64     }
    65 
    66     for(int j = 1; j <= k + 1; j++) {
    67         for(int i = 1; i <= n + 1; i++) {
    68             // f[i][j] = std::min(f[p][j - 1] + val
    69             if(j == 1) {
    70                 f[i][j] = c[i] + val(0, i);
    71             }
    72             else {
    73                 f[i][j] = INF;
    74                 for(int p = j - 1; p < i; p++) {
    75                     f[i][j] = std::min(f[i][j], f[p][j - 1] + val(p, i) + c[i]);
    76                 }
    77             }
    78         }
    79         if(j > 1) {
    80             ans = std::min(ans, f[n + 1][j]);
    81         }
    82     }
    83 
    84     printf("%lld", ans);
    85     return 0;
    86 }
    20分代码

    然后我又想到了网络流,发现好像可以搞成最大权闭合子图来做,但是那个k的限制不好处理.....

    最后光荣爆0了。

    正解是线段树优化DP,但是怎么个优化法呢?

    转移方程:f[i][j] = f[p][j - 1] + val(p, i) + c[i]

    黑科技就是用线段树维护等式右边......下标就是p。

    具体来说,我们当前正在考虑i。

    那么f[i][j]就是min(0, i - 1),直接在线段树上查询即可。

    线段树的初始值是f[p][j - 1],我们要动态的加上val(p, i)

    每个点都有一个last,表示在i ~ last这段区间建基站的话能覆盖到它。

    设last[v] = i,那么在i及之前的DP值都不会加上w[v],因为v之前的不会考虑到v,v ~ i的会覆盖到v。

    i及之后的,有一部分转移要加上w[v],就是first[v]之前的部分。

    因为first ~ i的转移会覆盖v,而i之后的转移会计算v。所以这些都不用计算v。

    此时我们把0 ~ first[v] - 1的区间全部加上w[i]即可。

    然后每次循环一个新j的时候把线段树初始化。

    细节处理繁多......

      1 #include <cstdio>
      2 #include <algorithm>
      3 #include <vector>
      4 
      5 typedef long long LL;
      6 const int N = 20010;
      7 const LL INF = 0x3f3f3f3f3f3f3f3f;
      8 
      9 LL d[N], s[N], f[N][210], c[N], w[N];
     10 int n, first[N], last[N], Time;
     11 std::vector<int> v[N];
     12 
     13 LL small[N << 2], tag[N << 2];
     14 
     15 inline void pushup(int o) {
     16     small[o] = std::min(small[o << 1], small[o << 1 | 1]);
     17     return;
     18 }
     19 
     20 inline void pushdown(int o) {
     21     if(tag[o]) {
     22         int ls = o << 1;
     23         int rs = ls | 1;
     24         tag[ls] += tag[o];
     25         tag[rs] += tag[o];
     26         small[ls] += tag[o];
     27         small[rs] += tag[o];
     28         tag[o] = 0;
     29     }
     30     return;
     31 }
     32 
     33 void add(int L, int R, LL v, int l, int r, int o) {
     34     if(L <= l && r <= R) {
     35         tag[o] += v;
     36         small[o] += v;
     37         return;
     38     }
     39     int mid = (l + r) >> 1;
     40     pushdown(o);
     41     if(L <= mid) {
     42         add(L, R,  v, l, mid, o << 1);
     43     }
     44     if(mid < R) {
     45         add(L, R, v, mid + 1, r, o << 1 | 1);
     46     }
     47     pushup(o);
     48     return;
     49 }
     50 
     51 LL ask(int L, int R, int l, int r, int o) {
     52     if(L <= l && r <= R) {
     53         return small[o];
     54     }
     55     int mid = (l + r) >> 1;
     56     pushdown(o);
     57     LL ans = INF;
     58     if(L <= mid) {
     59         ans = std::min(ans, ask(L, R, l, mid, o << 1));
     60     }
     61     if(mid < R) {
     62         ans = std::min(ans, ask(L, R, mid + 1, r, o << 1 | 1));
     63     }
     64     return ans;
     65 }
     66 
     67 void clear(int l, int r, int o) {
     68     tag[o] = 0;
     69     if(l == r) {
     70         small[o] = f[r - 1][Time - 1];
     71         return;
     72     }
     73     int mid = (l + r) >> 1;
     74     clear(l, mid, o << 1);
     75     clear(mid + 1, r, o << 1 | 1);
     76     pushup(o);
     77     return;
     78 }
     79 
     80 int main() {
     81     int k;
     82     LL ans = 0;
     83     scanf("%d%d", &n, &k);
     84     for(int i = 2; i <= n; i++) {
     85         scanf("%lld", &d[i]);
     86     }
     87     for(int i = 1; i <= n; i++) {
     88         scanf("%lld", &c[i]);
     89     }
     90     for(int i = 1; i <= n; i++) {
     91         scanf("%lld", &s[i]);
     92     }
     93     for(int i = 1; i <= n; i++) {
     94         scanf("%lld", &w[i]);
     95         ans += w[i];
     96         f[i][0] = f[i - 1][0] + w[i];
     97     }
     98 
     99     for(int i = 1; i <= n; i++) { // prework
    100         int l = 1, r = i;
    101         while(l < r) {
    102             int mid = (l + r) >> 1;
    103             if(d[mid] >= d[i] - s[i]) {
    104                 r = mid;
    105             }
    106             else {
    107                 l = mid + 1;
    108             }
    109         }
    110         first[i] = r;
    111         l = i;
    112         r = n;
    113         while(l < r) {
    114             int mid = (l + r + 1) >> 1;
    115             if(d[mid] <= d[i] + s[i]) {
    116                 l = mid;
    117             }
    118             else {
    119                 r = mid - 1;
    120             }
    121         }
    122         last[i] = r;
    123         v[r].push_back(i);
    124     }
    125 
    126     for(int i = 1; i <= n + 1; i++) {
    127         f[i][0] = INF;
    128     }
    129 
    130     for(int j = 1; j <= k + 1; j++) {
    131         Time = j;
    132         clear(1, n + 1, 1);
    133         for(int i = 0; i <= n + 1; i++) {
    134             // ask f[i][j]
    135             if(i >= j) {
    136                 f[i][j] = ask(1, i, 1, n + 1, 1) + c[i];
    137             }
    138             else {
    139                 f[i][j] = INF;
    140             }
    141             // insert
    142             for(int p = 0; p < v[i].size(); p++) {
    143                 add(1, first[v[i][p]], w[v[i][p]], 1, n + 1, 1);
    144             }
    145         }
    146         if(j > 1) {
    147             ans = std::min(ans, f[n + 1][j]);
    148         }
    149     }
    150 
    151     printf("%lld", ans);
    152     return 0;
    153 }
    AC代码

    思考:如果si表示能覆盖方圆si的村庄,又如何?

    转移方程写出来,发现就是一个简单的前缀最大值优化。要处理一下前缀和为负的这种情况...

  • 相关阅读:
    初识 MyBatis
    基于模板匹配的车牌识别
    完整java开发中JDBC连接数据库代码和步骤
    MyBatis 动态SQL
    最大子序列和问题
    二分搜索,欧几里德算法
    链表单链表
    UVA 12293 Box Game
    hdu 4565 so easy
    Bootstrap Table的使用 Cryst
  • 原文地址:https://www.cnblogs.com/huyufeifei/p/10271080.html
Copyright © 2020-2023  润新知