P2605 [ZJOI2010]基站选址
浪费了洛谷28发提交,有必要写篇题解
首先这个是dp无疑了。
考虑设状态。最容易想到的应该是: \(dp[i][j]\) 表示考虑到第 \(i\) 个位置,建 \(j\) 个基站的最小代价,答案是 \(\min\{dp[n][i]\}(0\le i \le k)\)
但是发现不好转移,那么换成:\(dp[i][j]\) 表示考虑到第 \(i\) 个位置,钦定第 \(i\) 个位置建基站,前 \(i\) 个位置总共建了 \(j\) 个基站的最小代价。
这样在统计答案的时候要改成 \(dp[i][j]+f(i,n)(1\le i \le n,0\le j \le k)\) ,\(f(l,r)\) 表示在 \(l\) 建基站,\([l,r]\) 中间总共需要赔偿多少。
那么转移很显然了,\(dp[i][j]=\min\{dp[k][j]+g(k,i) \} (0\le j<i)\) ,\(g(l,r)\) 表示在 \(l,r\) 都建基站,\([l,r]\) 中间总共需要赔偿多少。
边界:\(dp[0][0]=0\),
\(O(n^2k)\) 解决了。
然后我发现我只有 \(20\) 分。说好 \(40\%\) 的数据 \(n\le 500\) 呢?数组开到 \(1000\) 就过了 #4
,\(30\) 分。
为啥不是 \(40\) 啊,#2
怎么 WA
了啊?!然后陷入了困境,开始疯狂提交
忽然发现,边界应该是 \(dp[1][i]=h(1,i)\),\(h(l,r)\) 表示在 \(r\) 建基站 \([l,r]\) 中间总共需要赔偿多少。
因为如果第一维从 \(1\) 开始转移的话,我们上面转移方程默认的是 \(j\) 左边覆盖不到的 \(i\) 一定覆盖不到,所以只用考虑 \([j,i]\) 之间产生的贡献即可。但是一开始没有基站,所以这个条件不成立,然后就挂了。那怎么还有30分
现在就有 \(40\) 了。
后记:后面优化 \(dp\) 的时候拿暴力对拍发现 \(40\) 分的程序统计答案错了,但是它有 \(40\) 分。。。
接下去考虑优化 \(dp\) 。
\(f,h\) 两个函数都可以二分预处理出来(或者你看了后面的处理可以不用二分)。
这个转移方程长得是一个区间最小值的形式,就是 \(g(l,r)\) 特别不爽,没法优化。
这种时候往往考虑直接拆 \(g\) 函数,拆成最本质的形式。
我们发现一段区间 \([D[k]-S[k],D[k]+S[k]]\) 不包括 \(i,j\) 时 ,\(g(i,j)+=W[k]\) 。
对于一个确定的左端点 \(j\) ,它所能产生的贡献时确定的,主要是右端点 \(i\) 在不断右移,而不断右移就是一个 单调 的东西。
这意味着一旦 \(i\) 往右移动到某个临界点,某一些 \(k\) 也无法被 \(j\) 覆盖的时候,\(W[k]\) 的贡献必然产生,而且这个贡献是针对某一段 \(j\) 产生的。
到现在,线段树很明显了吧!
首先二分预处理每一个位置 \(i\) ,能覆盖它的最靠左的端点 \(L[i]\) 和最靠右的端点 \(R[i]\)。
对于每一个位置开 vector
,记录 \(R[j]=i\) 的区间编号 \(id\)。
每次从左往右扫,先查询 \([1,i-1]\) 的最小值更新 \(dp\) 值,再把 \([1,L[id]-1]\) 里的贡献加上 \(W[id]\) 。
其实 \(dp\) 数组可以开一维,迭代跑 \(k\) 次即可。
空间 \(O(n)\) ,时间复杂度 \(O(nk\log n)\)
//Orz cyn2006
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define mkp(x,y) make_pair(x,y)
#define fi first
#define se second
#define pb(x) push_back(x)
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f?x:-x;
}
#define N 20005
#define K 105
#define T (N<<2)
#define inf 1000000005
int n,k,D[N],C[N],S[N],W[N],dp[N],ans,L[N],R[N],pre[N],suf[N];
vector<pair<int,int> >v1[N],v2[N];
int val[T],tag[T];
#define lc (p<<1)
#define rc (p<<1|1)
void pushup(int p){val[p]=min(val[lc],val[rc]);}
void build(int l=1,int r=n,int p=1){
tag[p]=0;
if(l==r)return val[p]=min(dp[l],inf),void();
int mid=(l+r)>>1;
build(l,mid,lc),build(mid+1,r,rc);
pushup(p);
}
void pushdown(int p){
if(tag[p]){
val[lc]+=tag[p],val[rc]+=tag[p];
tag[lc]+=tag[p],tag[rc]+=tag[p];
tag[p]=0;
}
}
void update(int ql,int qr,int k,int l=1,int r=n,int p=1){
if(ql>qr)return;
if(ql<=l&&r<=qr)return val[p]+=k,tag[p]+=k,void();
pushdown(p);
int mid=(l+r)>>1;
if(ql<=mid)update(ql,qr,k,l,mid,lc);
if(mid<qr)update(ql,qr,k,mid+1,r,rc);
pushup(p);
}
int query(int ql,int qr,int l=1,int r=n,int p=1){
if(ql>qr)return inf;
if(ql<=l&&r<=qr)return val[p];
pushdown(p);
int mid=(l+r)>>1;
if(qr<=mid)return query(ql,qr,l,mid,lc);
if(mid<ql)return query(ql,qr,mid+1,r,rc);
return min(query(ql,qr,l,mid,lc),query(ql,qr,mid+1,r,rc));
}
signed main(){
n=read(),k=read();
for(int i=2;i<=n;++i)D[i]=read();
for(int i=1;i<=n;++i)C[i]=read();
for(int i=1;i<=n;++i)S[i]=read();
for(int i=1;i<=n;++i)W[i]=read();
for(int i=1;i<=n;++i){
L[i]=lower_bound(D+1,D+n+1,D[i]-S[i])-D;
R[i]=upper_bound(D+1,D+n+1,D[i]+S[i])-D-1;
v1[R[i]].pb(mkp(L[i],i)),v2[L[i]].pb(mkp(R[i],i));
}
for(int i=1;i<=n;++i){
pre[i]+=pre[i-1];
for(int j=0,sz=v1[i].size();j<sz;++j)pre[i+1]+=W[v1[i][j].se];
}
for(int i=n;i>=1;--i){
suf[i]+=suf[i+1];
for(int j=0,sz=v2[i].size();j<sz;++j)suf[i-1]+=W[v2[i][j].se];
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;++i)ans+=W[i];
if(!k)return printf("%d\n",ans),0;
for(int i=1;i<=n;++i)dp[i]=pre[i]+C[i],ans=min(ans,dp[i]+suf[i]);
for(int t=2;t<=k;++t){
build();
for(int i=1;i<=n;++i){
dp[i]=query(1,i-1)+C[i],ans=min(ans,dp[i]+suf[i]);
for(int j=0,sz=v1[i].size();j<sz;++j)update(1,v1[i][j].fi-1,W[v1[i][j].se]);
}
}
printf("%d\n",ans);
return 0;
}