洛谷P2605 基站選址
神TM毒瘤線段樹優化DP......新姿勢get。
題意:有n個村莊,在裏面選不多於k個建立基站。
建立基站要ci的費用。如果一個村莊方圓si內沒有基站,那麽又要支出wi的費用。求最小費用。
解:很顯然想到DP,f[i][j]表示前i個村莊裏面放了j個基站,其中第i個一定選的最小費用。費用只統計不超過i的。
轉移就是枚舉從p轉移,對於p到i的每一個,檢查是否需要付錢。
這樣是n3k的,只有20分。
1 #include <cstdio> 2 #include <algorithm> 3 4 typedef long long LL; 5 const int20分代碼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 }
然後我又想到了網絡流,發現好像可以搞成最大權閉合子圖來做,但是那個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的村莊,又如何?
轉移方程寫出來,發現就是一個簡單的前綴最大值優化。要處理一下前綴和為負的這種情況...
洛谷P2605 基站選址