problem
luogu-P2605
solution
首先,肯定都能想到最暴力的 dpdpdp。
dpi,j:idp_{i,j}:idpi,j:i 个村庄为止一共选了 jjj 个基站,且第 iii 个村庄一定建立基站的最小费用。
通过我们的定义可知第 nnn 个村庄一定被选,实际上未必。
所以我们可以建立第 n+1n+1n+1 个虚拟村庄,并要求改为建立 m+1m+1m+1 个基站。
设置信息:建立花费 c=0c=0c=0,赔偿花费 w=∞w=\inftyw=∞,距离 d=∞d=\inftyd=∞。这样就不会被其余村庄覆盖到也不会产生新花费而且一定建设。
边界我们设计好了,就看状态转移方程。
dpi,j=ci+min{dpk,j−1+pay(k,i)}k<idp_{i,j}=c_i+\min\Big\{dp_{k,j-1}+pay(k,i)\Big\}\quad k<idpi,j=ci+min{dpk,j−1+pay(k,i)}k<i。
注意到第二维 jjj 是可以滚动的,所以本质上是一维的 dpdpdp 转移 dpi=ci+mink<i{dpk+pay(k,i)}dp_{i}=c_i+\min_{k<i}\Big\{dp_{k}+pay(k,i)\Big\}dpi=ci+mink<i{dpk+pay(k,i)}。
pay(k,i):pay(k,i):pay(k,i): 相邻两个基站选址在 kkk 和 iii 村庄,这中间未被覆盖到的村庄一共支付的补偿费。
这其实很好计算。
因为距离 ddd 是递增的,我们完全可以用 lower_bound()
找到每个村庄能被覆盖到的最远左右村庄。不妨记为 sti/edist_i/ed_isti/edi。
即 [sti,edi][st_i,ed_i][sti,edi] 中有一个村庄建立了基站,那么 iii 村庄就可以被覆盖到,不需要支付补偿费。
通常遇到这种区间产生花费的,我们会选择前缀和优化,然后分离 i,ki,ki,k 各自的贡献,把只有 kkk 产生的贡献揉成一坨扔到线段树上,最小值查询,但自己想想这里的区间与通常情况的是不一样的。
通常情况的表述应为**“能覆盖 sis_isi 范围内的所有基站”,而不是此题的“被覆盖”**。
这里我们转化一下。
再看一遍一维 dpdpdp 的转移,这次从 dpkdp_kdpk 这里入手。
唯一给的限制是 k<ik<ik<i,所以 kkk 的取值是一段区间。
不难想到用线段树维护,把 dpkdp_kdpk 当成节点权值,然后查询区间最小值。
如果我们线段树查出来的 k<stxk<st_xk<stx,那么就需要支付 wxw_xwx。
而我们顺次考虑到村庄 i,stx≤i≤edxi,st_x\le i\le ed_xi,stx≤i≤edx 的时候,因为 iii 必建设基站,所以 xxx 村庄就会被覆盖。
于是我们想到将这样的 xxx 村庄挂到 edxed_xedx 上面去,可能多个 edxed_xedx 相同,用前向星/vector\text{vector}vector 存即可。
当 iii 考虑到 edxed_xedx,考虑完后,i←edx+1i\leftarrow ed_x+1i←edx+1,我们就要给线段树上 [1,stx)[1,st_x)[1,stx) 这些村庄加上 wxw_xwx 的花费。
就是个线段树区间加操作。
答案就是每一轮的 dpn+1dp_{n+1}dpn+1 取最小值。
就没了。时间复杂度,O(nmlogn)O(nm\log n)O(nmlogn),因为每个 xxx 只会挂在一个村庄下,遍历到某个村庄就把挂着的所有 xxx 的贡献一股脑往线段树上加,均摊下来是 O(1)O(1)O(1) 的。
注意: 最开始的,只建设一个基站的情况要特殊初始化 dpdpdp,后面才能以 j−1j-1j−1 个基站的 dpidp_idpi 建树。
code
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
#define maxn 20005
int n, k;
int d[maxn], c[maxn], s[maxn], w[maxn], st[maxn], ed[maxn], dp[maxn];
vector < int > G[maxn];namespace SGT {#define lson now << 1#define rson now << 1 | 1#define mid (l + r >> 1)struct node { int tag, val; }t[maxn << 2];void pushdown( int now ) {if( ! t[now].tag ) return;t[lson].tag += t[now].tag;t[lson].val += t[now].tag;t[rson].tag += t[now].tag;t[rson].val += t[now].tag;t[now].tag = 0;}void build( int now, int l, int r ) {t[now].tag = 0;if( l == r ) { t[now].val = dp[l]; return; }build( lson, l, mid );build( rson, mid + 1, r );t[now].val = min( t[lson].val, t[rson].val );}void modify( int now, int l, int r, int L, int R, int v ) {if( R < l or r < L ) return;if( L <= l and r <= R ) { t[now].val += v, t[now].tag += v; return; }pushdown( now );modify( lson, l, mid, L, R, v );modify( rson, mid + 1, r, L, R, v );t[now].val = min( t[lson].val, t[rson].val );}int query( int now, int l, int r, int L, int R ) {if( r < L or R < l ) return inf;if( L <= l and r <= R ) return t[now].val;pushdown( now );return min( query( lson, l, mid, L, R ), query( rson, mid + 1, r, L, R ) );}
}signed main() {scanf( "%lld %lld", &n, &k );for( int i = 2;i <= n;i ++ ) scanf( "%lld", &d[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &c[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &s[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &w[i] );++ n, ++ k, w[n] = d[n] = inf;for( int i = 1;i <= n;i ++ ) {ed[i] = upper_bound( d + 1, d + n + 1, d[i] + s[i] ) - d - 1;st[i] = lower_bound( d + 1, d + n + 1, d[i] - s[i] ) - d;G[ed[i]].push_back( i );}for( int i = 1, now = 0;i <= n;i ++ ) {dp[i] = now + c[i];for( int j : G[i] ) now += w[j];}int ans = dp[n];for( int j = 2;j <= k;j ++ ) {SGT :: build( 1, 1, n );for( int i = 1;i <= n;i ++ ) {dp[i] = c[i] + SGT :: query( 1, 1, n, 1, i - 1 );for( int p : G[i] ) SGT :: modify( 1, 1, n, 1, st[p] - 1, w[p] );}ans = min( ans, dp[n] );}printf( "%lld\n", ans );return 0;
}