【問題描述】有N個村莊坐落在一條直線上,第i(i>1)個村莊距離第1個村莊的距離為Di。需要在這些村莊中建立不超過K個通訊基站,在第i個村莊建立基站的費用為Ci。如果在距離第i個村莊不超過Si的範圍內建立了一個通訊基站,那麼就成它被覆蓋了。如果第i個村莊沒有被覆蓋,則需要向他們補償,費用為Wi。現在的問題是,選擇基站的位置,使得總費用最小。【輸入格式】base.in輸入檔案的第一行包含兩個整數N,K,含義如上所述。第二行包含N-1個整數,分別表示D2,D3,…,DN ,這N-1個數是遞增的。第三行包含N個整數,表示C1,C2,…CN。第四行包含N個整數,表示S1,S2,…,SN。第五行包含N個整數,表示W1,W2,…,WN。【輸出格式】base.out輸出檔案中僅包含一個整數,表示最小的總費用。【輸入範例】3 21 22 3 21 1 010 20 30【輸出範例】4【資料規模】40%的資料中,N<=500;100%的資料中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。
此題考察用線段樹最佳化的動態規劃。
首先不難想到樸素方法:
狀態:f[k][i]表示將第k個基站建在第i個村莊的前i個村莊的最小費用。
f[k][i] = min(f[k - 1][p] + cost(p, i)) + c[i]。
這樣做的時間複雜度為O(n^3·k),顯然會逾時。
一種想法是,最佳化cost(p, i)的計算,使得減少枚舉量,但是這樣複雜度還是較高。
進一步思考,設村莊i的不被賠償範圍為[L[i], R[i]],那麼可以看出,隨著k和i的遞增枚舉(k在外層迴圈),若在某個位置i建立基站,在i左邊的村莊p需要被賠償,那麼在i之後的i',p也一定需要被賠償。
於是可以開個數組維護各個位置當前的費用,每次在其中找到最小的費用,再加上c[i]即可。
但是這樣時間效率仍然較低。
說起區間,不難想到線段樹,對!就是它了,可以把時間複雜度最佳化到(O(k·nlog n))。
把各個村莊按R從小到大的順序排序,每次枚舉i先將各個點的費用設為f[k - 1]中對應值,然後枚舉各個p(R[p] < d[i]),將所有的w[p]全部加到對應的區間[0, L[p])中,然後利用線段樹中維護的最小值來計算當前的f[k][i],即用這個最小值加上c[i]。
更新ans的值時,先繼續把枚舉i時還剩下的p按照上述規則依次加入到線段樹中,這時再用線段樹維護的最小值來更新ans。
Accode:
#include <cstdio>#include <cstdlib>#include <algorithm>#include <string>#define min(a, b) ((a) < (b) ? (a) : (b))const char fi[] = "base.in";const char fo[] = "base.out";const int maxN = 20010;const int MAX = 0x3f3f3f3f;const int MIN = ~MAX;struct SegTree {int L, R, lc, rc, Min, sum;};SegTree tr[maxN << 1];int f[maxN], L[maxN], R[maxN];int pre[maxN], ord[maxN];int d[maxN], c[maxN], s[maxN], w[maxN];int n, K, tot;void init_file(){ freopen(fi, "r", stdin); freopen(fo, "w", stdout); return;}inline int getint(){ int res = 0; char tmp; while (!isdigit(tmp = getchar())); do res = (res << 3) + (res << 1) + tmp - '0'; while (isdigit(tmp = getchar())); return res;}inline bool cmpL(const int &a, const int &b){return L[a] < L[b];}inline bool cmpR(const int &a, const int &b){return R[a] < R[b];}void readdata(){ n = getint(); K = getint(); for (int i = 2; i <= n; ++i) d[i] = getint(); for (int i = 1; i <= n; ++i) c[i] = getint(); for (int i = 1; i <= n; ++i) s[i] = getint(); for (int i = 1; i <= n; ++i) w[i] = getint(); for (int i = 1; (ord[i] = i) <= n; ++i) L[i] = d[i] - s[i], R[i] = d[i] + s[i]; std::sort(ord + 1, ord + n + 1, cmpL); for (int i = 1, p = 1; i <= n; ++i) while (p <= n && L[ord[p]] <= d[i]) pre[ord[p++]] = i;//用於記錄該點左邊第一個在其不賠償範圍之內的點。 std::sort(ord + 1, ord + n + 1, cmpR); return;}inline void pushdown(int p){ tr[tr[p].lc].sum += tr[p].sum; tr[tr[p].rc].sum += tr[p].sum; tr[tr[p].lc].Min += tr[p].sum; tr[tr[p].rc].Min += tr[p].sum; tr[p].sum = 0; return;}inline void update(int p){ tr[p].Min = min(tr[tr[p].lc].Min, tr[tr[p].rc].Min); return;}void build(int L, int R){ int Now = ++tot; tr[Now].L = L, tr[Now].R = R, tr[Now].sum = 0; if (L == R) {tr[Now].Min = f[L]; return;} int Mid = (L + R) >> 1; tr[Now].lc = tot + 1; build(L, Mid); tr[Now].rc = tot + 1; build(Mid + 1, R); update(Now); return;}void Add(int p, int L, int R, int delta){ if (L <= tr[p].L && R >= tr[p].R) { tr[p].sum += delta, tr[p].Min += delta; return; } pushdown(p); //標記需要向下傳。 int Mid = (tr[p].L + tr[p].R) >> 1; if (L <= Mid) Add(tr[p].lc, L, R, delta); if (Mid < R) Add(tr[p].rc, L, R, delta); update(p); return;}void work(){ memset(f, 0x3f, sizeof f); f[0] = 0; int ans = MAX; for (int k = 0, p, i; k < K + 1; ++k) { tot = 0; build(0, n);//為了方便計算,虛擬一個節點0出來。 for (i = p = 1; i < n + 1; ++i) { for (; p <= n && R[ord[p]] < d[i]; ++p) Add(1, 0, pre[ord[p]] - 1, w[ord[p]]); f[i] = tr[1].Min + c[i]; } for (; p <= n; ++p) Add(1, 0, pre[ord[p]] - 1, w[ord[p]]); //要把i之後的費用全部加到其中,這樣才是真正的總費用。 ans = min(ans, tr[1].Min); } printf("%d\n", ans); return;}int main() { init_file(); readdata(); work(); return 0;}#undef min