[ZJOI2010] 基站选址 题解

前言

题目链接:洛谷

题意简述

[ZJOI2010] 基站选址。

\(N\) 个村庄坐落在一条直线上,第 \(i\) 个村庄距离第 \(1\) 个村庄的距离为 \(D_i\)。需要在这些村庄中建立不超过 \(K\) 个通讯基站,在第 \(i\) 个村庄建立基站的费用为 \(C_i\)。如果在距离第 \(i\) 个村庄不超过 \(S_i\) 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 \(i\) 个村庄没有被覆盖,则需要向他们补偿,费用为 \(W_i\)。请选择基站的位置,使得总费用最小。

题目分析

显然考虑使用 DP。状态也很好想,用 \(f[i][j]\) 表示考虑了前 \(i\) 个村庄,在第 \(i\) 个村庄上有一个基站,一共建了 \(j\) 个基站,\(i\) 个村庄的花费。

思考这个状态为什么是对的。也即为什么记录\(i\) 个村庄的花费是正确的。发现,如果第 \(i\) 个位置建了基站,前面的村庄都覆盖不到的话,再在后面建基站,也是不会覆盖得到的。这样就没有了后效性,这个状态是正确的。

很容易得到转移方程:

\[f[i][j] = \min _ {k = 0} ^ {i - 1} \Big \lbrace f[k][j - 1] + cost[k][i] + C_i \Big \rbrace \]

其中,\(cost[l][r]\) 表示如果在 \(l\)\(r\) 处建有基站,在 \(l \sim r\) 中,不能被覆盖到的村庄的赔偿总和。用形式化的语言表示如下,在条件中已经暗含了 \(i \in [l, r]\) 这个限制:

\[cost[l][r] = \sum _ {D_l < D_i - S_i \wedge D_i + S_i < D_r} W_i \]

二维数点问题吧。考虑再简化一下,记 \(L[i]\) 表示 \(i\) 左边最远能覆盖到 \(i\) 的基站。用形式化的语言表示如下:

\[L[i] = \min _ {D_j \geq D_i - S_i} \Big \lbrace j \Big \rbrace \]

同理记 \(R[i]\) 表示 \(i\) 右边最远能覆盖到 \(i\) 的基站。

\[R[i] = \max _ {D_j \leq D_i + S_i} \Big \lbrace j \Big \rbrace \]

这个可以用二分预处理。那么 \(cost[l][r]\) 的表达式变为了:

\[cost[l][r] = \sum _ {l < L[i] \wedge R[i] < r} W_i \]

考虑 \(r\) 从小到大的扫描线,维护一颗线段树,记录在当前右端点为 \(r\) 的所有 \(cost[l][r]\) 的值。查询完后,对所有 \(R[i] = r\)\(i\),对 \(0 \sim L[i] - 1\) 增加 \(W_i\)。可以画出坐标系理解一下。

我们显然不能预处理出所有的 \(cost[l][r]\),所以只能边 DP 边查询。不妨把最外层的状态去掉,记 \(g[i] = f[i][j - 1]\),那么重写转移方程:

\[\begin{aligned} f[i] &= \min _ {k = 0} ^ {i - 1} \Big \lbrace g[k] + cost[k][i] + C_i \Big \rbrace \\ &= \min _ {k = 0} ^ {i - 1} \Big \lbrace g[k] + cost[k][i] \Big \rbrace + C_i \end{aligned} \]

所以用来扫描线的线段树,建树时把 \(g[k]\) 作为初值,这样每次只用查询线段树里 \(0 \sim i - 1\) 的最小值就行了。

初始 \(f[0] = 0\),其它 \(f[i] = \infty\)。答案 \(ans = \min \limits _ {k = 0} ^ {n} \Big \lbrace g[k] + cost[k][n + 1] \Big \rbrace\),也即线段树查询 \(0 \sim n\) 的最小值。

时间复杂是:\(\Theta(nk \log n)\)

代码

Updated on: 2024.11.19:更新代码和部分分。

$\mathcal{O}(n^3k)$ 部分分代码

状态和上文略有不同,交换了两维。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int N = 20010, K = 110;

int n, k, d[N],c[N], s[N], w[N];
int dp[K][N];

signed main() {
    scanf("%d%d", &n, &k), d[0] = -0x3f3f3f3f, d[n + 1] = 0x3f3f3f3f;
    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]);
    memset(dp, 0x3f, sizeof dp);
    dp[0][0] = 0;
    for (int i = 1; i <= k + 1; ++i) {
        for (int j = 1; j <= n + 1; ++j) {
            for (int o = 0; o < j; ++o) {
                if (dp[i - 1][o] == 0x3f3f3f3f) continue;
                int cost = 0;
                for (int _ = o + 1; _ < j; ++_)
                    if (d[_] - d[o] > s[_] && d[j] - d[_] > s[_])
                        cost += w[_];
                dp[i][j] = min(dp[i][j], dp[i - 1][o] + cost + c[j]);
            }
        }
    }
    int ans = 0x3f3f3f3f;
    for (int i = 1; i <= k + 1; ++i)
        ans = min(ans, dp[i][n + 1]);
    printf("%d", ans);
    return 0;
}
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 20010, K = 110;

int n, k, d[N], c[N], s[N], w[N];
int L[N], R[N];

int dp[N];

#define lson (idx << 1    )
#define rson (idx << 1 | 1)

int tag[N << 2], mi[N << 2];

inline void pushup(int idx) {
    mi[idx] = min(mi[lson], mi[rson]);
}

inline void pushtag(int idx, int v) {
    tag[idx] += v, mi[idx] += v;
}

inline void pushdown(int idx) {
    if (!tag[idx]) return;
    pushtag(lson, tag[idx]), pushtag(rson, tag[idx]);
    tag[idx] = 0;
}

void build(int idx, int l, int r) {
    tag[idx] = 0;
    if (l == r) return mi[idx] = dp[l], void();
    int mid = (l + r) >> 1;
    build(lson, l, mid), build(rson, mid + 1, r);
    pushup(idx);
}

void modify(int idx, int trl, int trr, int l, int r, int v) {
    if (l <= trl && trr <= r) return pushtag(idx, v);
    pushdown(idx);
    int mid = (trl + trr) >> 1;
    if (l <= mid) modify(lson, trl, mid, l, r, v);
    if (r >  mid) modify(rson, mid + 1, trr, l, r, v);
    pushup(idx);
}

int query(int idx, int trl, int trr, int l, int r) {
    if (l <= trl && trr <= r) return mi[idx];
    pushdown(idx);
    int mid = (trl + trr) >> 1;
    int res = 0x3f3f3f3f;
    if (l <= mid) res = min(res, query(lson, trl, mid, l, r));
    if (r >  mid) res = min(res, query(rson, mid + 1, trr, l, r));
    return res;
}

#undef lson
#undef rson

vector<pair<int, int>> line[N];

signed main() {
    scanf("%d%d", &n, &k), d[0] = -0x3f3f3f3f, d[n + 1] = 0x3f3f3f3f;
    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]);
    for (int i = 1; i <= n; ++i) {
        L[i] = lower_bound(d, d + i, d[i] - s[i]) - d;
        R[i] = upper_bound(d + i + 1, d + n + 1, d[i] + s[i]) - d - 1;
        if (L[i] - 1 >= 0 && R[i] + 1 <= n + 1)
            line[R[i] + 1].emplace_back(L[i] - 1, w[i]);
    }
    memset(dp, 0x3f, sizeof dp), dp[0] = 0;
    int ans = 0x3f3f3f3f;
    for (int i = 1; i <= k + 1; ++i) {
        build(1, 0, n + 1);
        for (int j = 1; j <= n + 1; ++j) {
            for (auto [k, v] : line[j]) modify(1, 0, n + 1, 0, k, v);
            dp[j] = query(1, 0, n + 1, 0, j - 1) + c[j];
        }
        ans = min(ans, dp[n + 1]);
    }
    printf("%d", ans);
    return 0;
}
24-7-4 代码:略去快读快写。
// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main() { return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>
#include <vector>

int n, k, ans = 0x3f3f3f3f;
int d[20010], c[20010], r[20010], w[20010];
int L[20010], R[20010], f[20010];
vector<pair<int, int>> yzh[20010];

struct Segment_Tree {
	#define lson (idx << 1    )
	#define rson (idx << 1 | 1)
	
	struct node {
		int l, r;
		int mi, lazy;
	} tree[20010 << 2];
	
	void pushup(int idx) {
		tree[idx].mi = min(tree[lson].mi, tree[rson].mi);
	}
	
	void build(int idx, int l, int r) {
		tree[idx] = {l, r, 0x3f3f3f3f, 0};
		if (l == r) return tree[idx].mi = f[l], void();
		int mid = (l + r) >> 1;
		build(lson, l, mid), build(rson, mid + 1, r);
		pushup(idx);
	}
	
	void pushtag(int idx, int val) {
		tree[idx].lazy += val;
		tree[idx].mi += val;
	}
	
	void pushdown(int idx) {
		if (!tree[idx].lazy) return;
		pushtag(lson, tree[idx].lazy);
		pushtag(rson, tree[idx].lazy);
		tree[idx].lazy = 0;
	}
	
	void modify(int idx, int l, int r, int val) {
		if (tree[idx].l > r || tree[idx].r < l) return;
		if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, val);
		pushdown(idx);
		modify(lson, l, r, val);
		modify(rson, l, r, val);
		pushup(idx);
	}
	
	int query(int idx, int l, int r) {
		if (tree[idx].l > r || tree[idx].r < l) return 0x3f3f3f3f;
		if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].mi;
		pushdown(idx);
		return min(query(lson, l, r), query(rson, l, r));
	}
	
	void output(int idx) {
		if (tree[idx].l == tree[idx].r) {
			cout << tree[idx].mi << " \n"[tree[idx].l == n] << flush;
			return;
		}
		pushdown(idx);
		output(lson);
		output(rson);
	}
	
	#undef lson
	#undef rson
} tree;

signed main() {
	fread(buf, 1, MAX, stdin);
	read(n), read(k), d[0] = -0x3f3f3f3f;
	for (int i = 2; i <= n; ++i) read(d[i]);
	for (int i = 1; i <= n; ++i) read(c[i]);
	for (int i = 1; i <= n; ++i) read(r[i]);
	for (int i = 1; i <= n; ++i) read(w[i]);
	for (int i = 1; i <= n; ++i) {
		L[i] = lower_bound(d, d + n + 1, d[i] - r[i]) - d;
		R[i] = upper_bound(d, d + n + 1, d[i] + r[i]) - d - 1;
		yzh[R[i]].push_back({L[i], w[i]});
		f[i] = 0x3f3f3f3f;
	}
	for (int i = 0; i <= k; ++i) {
		tree.build(1, 0, n);
		for (int j = 1; j <= n; ++j) {
			f[j] = tree.query(1, 0, j - 1) + c[j];
			for (const auto& [l, w]: yzh[j])
				tree.modify(1, 0, l - 1, w);
		}
		ans = min(ans, tree.query(1, 0, n));
	}
	printf("%d", ans);
	return 0;
}

后记 & 反思

据说有 \(\Theta(n \log n \log w)\) 的做法?但是我太蒻了,并不会。

模拟赛的时候没想到 DP 的时候只用考虑\(i\) 个村庄的花费,而是用总的减去被覆盖到的,虽说本质相同,但是难分析。

挺板子的,但是没做出来。可能是时间不够了?

posted @ 2024-07-04 11:22  XuYueming  阅读(40)  评论(1编辑  收藏  举报