题意:
给你n个点,一个覆盖范围k,编号[1,n],点i与网络直接连接的代价是i,如果这个点是关键点,那么让它与网络直接连接后它可以让编号在[i-k,i+k]的点与网络间接连接,求让全部点和网络连接的最小代价。
题解:
这题感觉蛮难想,一开始往贪心想,不太好贪,再想DP,又觉得前后都会影响,想不清楚。这里其实还是DP。
用dp[i]表示把[1,i]的点全部连接需要的最小花费,那么对于dp[i],有两种情况:
- 它花费i直接与网络相连
- 它通过它前面的点间接与网络相连
情况1直接就是
d
p
[
i
]
=
d
p
[
i
−
1
]
+
i
dp[i] = dp[i-1]+i
dp[i]=dp[i−1]+i
对于情况2,应该是
d
p
[
i
]
=
m
i
n
(
v
a
l
[
j
]
)
dp[i] = min(val[j])
dp[i]=min(val[j]),j代表[i-k,i-1]中的关键点,val[j]是[1,j]都连起来且j直接和网络相连的最小花费。
那么可以看出
v
a
l
[
j
]
=
m
i
n
(
d
p
[
i
]
)
+
j
val[j]=min(dp[i])+j
val[j]=min(dp[i])+j,其中i的范围是[j-k-1,j-1]。
那么现在我们就可以维护dp数组和val数组,然后互相推了。这里题解用multiset去维护可以用的dp和val值,看着挺方便的。
如果我自己推出这个式子我应该会打两个线段树
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 2e5 + 50;
int n, k;
char s[maxn];
multiset<ll> s1, s2;//s1是当前可以触摸到的位置的dp值,s2可以更新当前点的dp值
vector<ll> del[maxn];//当前点开始就不能使用的s2中的值
ll dp[maxn];//dp[i]表示把[1,i]连接起来需要的最小花费
int main()
{
cin>>n>>k;
scanf("%s", s+1);
dp[0] = 0;
s1.insert(dp[0]);
for(int i = 1; i <= n; ++i){
dp[i] = dp[i-1] + i;//先直接连接
if(i-k-2 >= 0) s1.erase( s1.find(dp[i-k-2]) );
for(int j = 0; j < del[i].size(); ++j) s2.erase( s2.find(del[i][j]) );//维护s1和s2保证它们当前存储的值可用
if( s2.size() ) dp[i] = min(dp[i], *s2.begin());
if(s[i] == '1'){
ll val;//当前点作为路由器连接所需要花费的最小费用
if(s1.size()) val = *s1.begin() + i;
else val = i;
dp[i] = min(dp[i], val);
s2.insert(val);
if(i+k+1 <= n) del[i+k+1].push_back(val);//一直到i+k为止都可以使用这个val去更新dp
}
s1.insert(dp[i]);
}
cout<<dp[n]<<endl;
}