2022/09/26小测 题解
(本文将笔者测试时的想法及赛后想法均写出来了)
题目:
话说某某在 \(cj\) 校运会上异军突起,其实不是偶然,而是有历史原因的。
时光回溯到 \(XX\) 年前,某某为了心中的理想,每天爬 \(N\) 里山路上学。直到有一天 \(mlj\) (也就是战神 \(Mars\))来到这里,被某某所打动,于是决定帮某某一把。从某某家到学校中间的这 \(N\) 里山路在一条直线上,第 \(i\) 里山路的海拔高度为 \(H_i\) ,如果一段相同高度的山路两边都比它低或者是山的边界,那么这段山路将被称之为“山顶”。$mlj $想这连绵起伏的山路爬着多累啊,于是他决定动用神力,降低某些山路的海拔高度使得山顶的个数不超过 \(K\)。但 \(mlj\) 不想做得太明显而被某某发现,于是他求助于你。
请求出要使“山顶”的数目不超过k,所有山路降低的高度之和至少是多少。
分析过程:
-
[\(1\)] : 因为要求我们保留最后 \(k\) 个山顶后,所删除的高度最小,首先我们考虑贪心,我们先考虑将这个图形分一下层,那么每次一段区间两端出现 \(0\) 时,那么这段区间就有 至少一个 山顶。但是由于笔者认为太复杂就换了一种错误的想法。下面先说一下错误的想法(可略过)。
-
[\(2\)]:我们考虑先找到所有的山顶,然后利用贪心的思想去掉多余的最小的山顶,但是这种做法会忽视掉一个大山顶上包含若干个小山顶的情况,那么我们就要解决这个问题。
-
[\(3\)]:我们可以利用线段树来维护某一段区间的山顶个数,然后从顶往去减掉多余的山顶,减到只剩一个的时候停止。而这种做法太复杂了,我们就又回到了分层的想法。我们考虑将这个图按高度分一下层,然后先求出大的山顶,若大山顶个数大于 \(k\) ,那么我们就可以直接选取最小的去删掉,然后用线段树标记一下这个区间。那这种贪心策略是否正确呢,显然不正确。假如说某一个山顶上恰好有 \(k\) 个山顶,而与他同层的也恰好有 \(k\) 个,那么我们怎样处理呢。我们很容易想到用 \(dp\) 来将子问题处理出来再来退出整个的大问题。我们可以出来每一层的山顶个数,和他所在的大山顶。那这样我们就可以进行 \(dp\) 了。
-
[\(4\)]:我们考虑如何进行 \(dp\),我们可以设 \(dp_{i,j}\) 为在 \(i\) 号大山顶上删除 \(j\) 个小山顶的最小高度。我们观察这个定义,是不是很像树形 \(dp\) ,没错,正解就是树形 \(dp\),而且是一个书上背包的板子题吧。至此我们的分析过程结束。
代码实现 :
- [\(1\)]:我们要如何处理山顶呢?笔者自己想到的是对高度离散化一下,在逐一去掉某一个高度来确定山顶。但是题解真的太妙了!!!。我们可以利用
单调栈,来处理山顶。
具体实现如下:
\(1.\):当前高度大于栈顶的高度,那么就将他直接放入栈就中了。
\(2.\):当前高度等于栈顶高度,我们直接累加个数就可以了,为什么要记录个数呢,因为我们要处理高度
\(3.\):当前高度小于栈顶高度,我们直接退栈即可,然后将他的高度加到他的下一个元素,因为他依靠的山顶一定包含他的高度,在退栈时我们进行连边。
具体的来看代码吧
view
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e6 + 7;
struct T {int v , val;};
vector<T> e[M];
int n , k , cnt , tot , root;
int h[M] , dp[M][27] , id[M] , size[M];
int num[M] , s[M];
void dfs(int x , int fa) {
size[x] = 1;
bool f = 1;
for(auto i : e[x]) {
dfs(i.v , x);
f = 0;
size[x] += size[i.v];
for(int j = 0; j < size[x]; ++ j) {
for(int l = 1; l <= min(size[i.v] - 1, j); ++ l) {
dp[x][j] = min(dp[i.v][l] + dp[x][(j - l) > 0 ? j - l : 1] , dp[x][j] + (dp[i.v][0] + size[i.v] * i.val));
}
}
}
if(f) dp[x][0] = 0;
}
signed main () {
ios::sync_with_stdio(0),cin.tie(0);
cin >> n >> k;
int minn = 0x7fffffff;
int maxx = 0;
for(int i = 1; i <= n; ++ i) {cin >> h[i];minn = min(minn , h[i]);}
h[0] = minn , h[n + 1] = minn;
for(int i = 0; i <= n + 1; ++ i) {
while(h[i] < s[cnt] && cnt > 0) {
e[id[cnt]].push_back({id[cnt - 1] , num[cnt]});
e[id[cnt - 1]].push_back({id[cnt],num[cnt]});
num[cnt - 1] += num[cnt];
num[cnt] = 0;
cnt --;
}
if(h[i] > s[cnt]) s[++ cnt] = h[i] , num[cnt] ++ , id[cnt] = ++ tot;
else if(h[i] == s[cnt]) num[cnt] ++;
}
root = 1;
dfs(1 , 0);
cout << dp[1][k];
}