题目链接:https://www.luogu.com.cn/problem/P2605
题目的条件数据比较多,在一个地方建基站会影响左右两边
看看数据范围,考虑
d
p
dp
dp
因为基站会影响左右两边,设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示在前
j
j
j个村庄中建
i
i
i个基站,且最后一个基站建在第
j
j
j个位置,
v
a
l
(
i
,
j
)
val(i,j)
val(i,j)代表
i
i
i至
j
j
j间村庄
w
w
w的花费(注意:
j
j
j代表村庄)
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
k
]
+
v
a
l
(
k
,
i
)
)
dp[i][j]=min(dp[i-1][k]+val(k,i))
dp[i][j]=min(dp[i−1][k]+val(k,i))
直接dp肯定会爆
重点在于如何维护
v
a
l
(
k
,
j
)
val(k,j)
val(k,j)
v
a
l
(
i
,
j
)
val(i,j)
val(i,j)不具有单调性,考虑用数据结构维护
考虑任意结点
x
x
x的
w
[
x
]
w[x]
w[x]对答案的贡献
发现是从1开始的一段连续区间,保证
d
[
x
]
−
d
[
k
]
>
s
[
x
]
d[x]-d[k]>s[x]
d[x]−d[k]>s[x]
再看看
d
p
dp
dp方程,发现
k
<
=
100
k<=100
k<=100(题目中)想到可能会用到线段树
第一维枚举
k
k
k(建站个数)变量为
i
i
i
将
i
−
1
i-1
i−1的
d
p
dp
dp值建一棵线段树
第j个结点代表
d
p
[
i
−
1
]
[
j
−
1
]
dp[i-1][j-1]
dp[i−1][j−1]
在从
1
1
1枚举到
n
n
n,枚举到
j
j
j单点加入
x
x
x,保证
d
[
j
]
−
d
[
x
]
>
=
s
[
x
]
d[j]-d[x]>=s[x]
d[j]−d[x]>=s[x]
找出满足
d
[
x
]
−
d
[
k
]
>
s
[
x
]
d[x]-d[k]>s[x]
d[x]−d[k]>s[x]的区间,区间修改
注意:
x
x
x的顺序不一定是原数组(被害调1.5h)
然后是根据
d
p
dp
dp方程区间查询
最后找答案我是重新建树 强行增大常数
就可以了
时间复杂度
O
(
k
n
l
o
g
n
)
O(knlogn)
O(knlogn)
C o d e Code Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=2e4,MAXK=100,inf=1e15;
struct SEG{
int l,r,mn,tag;
}tree[MAXN<<2|10];
int dp[MAXK+10][MAXN+10];
int d[MAXN+10],c[MAXN+10],s[MAXN+10],w[MAXN+10],lx[MAXN+10];
//lx[i]代表i前面建基站能覆盖到i的最远村庄
struct ww{
int op,val;
}q[MAXN+10]; //q为单点加入x的顺序
inline int read();
inline bool cmp(const ww&,const ww&);
inline void pushup(int);
inline void pushdown(int);
void build(int,int,int,int);
void update(int,int,int,int);
int query(int,int,int);
signed main(){
//freopen ("std.in","r",stdin);
//freopen ("std.out","w",stdout);
int n,k;
n=read(),k=read();
for (register int i=1;i<n;++i) d[i+1]=read();
for (register int i=1;i<=n;++i) c[i]=read();
for (register int i=1;i<=n;++i) s[i]=read(),q[i].val=d[i]+s[i],q[i].op=i;
for (register int i=1;i<=n;++i) w[i]=read(),dp[0][i]=inf;
for (register int i=1;i<=n;++i) lx[i]=lower_bound(d+1,d+i,d[i]-s[i])-d;
sort(q+1,q+n+1,cmp);
int l,ans=inf;
for (register int i=1;i<=k;++i){
dp[i][0]=inf;l=1;
build(i-1,1,n,1);
for (register int j=1;j<=n;++j){
while (l<j && d[j]-d[q[l].op]>s[q[l].op]) update(1,lx[q[l].op],1,w[q[l].op]),++l; //加入覆盖不到的点的贡献
dp[i][j]=query(1,j,1)+c[j]; //更新dp值
}
//统答案
ans=min(ans,dp[i][n]);
build(i,1,n,1);
for (register int j=1;j<=n;++j) update(1,lx[j],1,w[j]);
ans=min(query(1,n,1),ans);
}
printf("%lld\n",ans);
return 0;
}
inline int read(){
int x=0;
char c=getchar();
while (!isdigit(c))c=getchar();
while (isdigit(c))x=(x<<1)+(x<<3)+(c&15),c=getchar();
return x;
}
inline bool cmp(const ww &x,const ww &y){
return x.val<y.val;
}
inline void pushup(int s){
tree[s].mn=min(tree[s<<1].mn,tree[s<<1|1].mn);
}
inline void pushdown(int s){
int val=tree[s].tag;
tree[s<<1].mn+=val;
tree[s<<1].tag+=val;
tree[s<<1|1].mn+=val;
tree[s<<1|1].tag+=val;
tree[s].tag=0;
}
void build(int k,int l,int r,int s){
tree[s].l=l,tree[s].r=r,tree[s].tag=0;
if (l==r){
tree[s].mn=dp[k][l-1];
return;
}
int mid=l+r>>1;
build(k,l,mid,s<<1);
build(k,mid+1,r,s<<1|1);
pushup(s);
}
void update(int l,int r,int s,int val){
if (tree[s].l>r || tree[s].r<l) return;
if (l<=tree[s].l && tree[s].r<=r){
tree[s].mn+=val;
tree[s].tag+=val;
return;
}
pushdown(s);
update(l,r,s<<1,val);
update(l,r,s<<1|1,val);
pushup(s);
}
int query(int l,int r,int s){
if (tree[s].l>r || tree[s].r<l) return inf;
if (l<=tree[s].l && tree[s].r<=r) return tree[s].mn;
pushdown(s);
return min(query(l,r,s<<1),query(l,r,s<<1|1));
}
将
x
x
x的更新顺序直接和原数组相同的错误数据
输入:
8 2
2 3 11 16 17 18 21
4 3 14 2 5 14 6 10
10 9 4 4 9 7 5 3
6 10 11 9 8 13 6 7
正确输出:
18