[ZJOI2010] 基站选址(线段树优化dp)
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+min?k<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(nmlog?n)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; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的[ZJOI2010] 基站选址(线段树优化dp)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 铭凡 UM780 XTX 迷你主机售价
- 下一篇: [ZJOI2010] 贪吃的老鼠(二分+