题解 | #The Great Wall II# #牛客多校# #单调栈# 超详细解析
The Great Wall II
https://ac.nowcoder.com/acm/contest/33194/I
牛客331194I多校 - The Great Wall II
题意
- 给出一个长度为 的序列,你需要将序列切割成 段,每段对答案的贡献为这一段的最大值。
- 需要最小化答案。
- 你需要对于所有的 ,回答每个询问。
思路
初步思路
- 考虑 DP。
- 令 为前 个物品,分成 段,的最小值。
- 转移:
- 复杂度:。
优化
- 观察转移方程:
- 我们是否可以对于每个 ,使用单调栈快速地求出 实现 的转移?
- 不可以。 的确是最小的,但是加上后面的那个最大值,就不一定是最优的了。
证明:这里我们分析区间 。随着 指针的向左移动, 单调递减,但是 单调递增。
- 我们能观察到,在上面的转移方程中,随着 指针的向左移动, 单调递减,但是 单调递增。
- 所以对于一个 而言,在序列中存在一段区间 ,满足
- 这段区间之前的部分,也就是 ,满足
- 转移:
- 对于 ,
- 对于 ,
- 以上转移可以用单调栈维护。
- 既然要找分界点 ,那么单调栈一定要维护当前 的值,而且这个单调栈是由 的单调递减的单调栈。
- 显然,还要维护当前的 值 ,和之前的最小的 值 。
- 转移:
- 弹栈条件:,弹栈的同时
- 弹栈停止意味着找到了分界点。此时在这之前意味着满足 的条件,那么
- 当前的 值就是 。
- 入栈:,
代码
#include <cstdio>
#include <iostream>
#include <stack>
#define int long long
const int N = 8010;
const int INF = 1e9;
using namespace std;
struct STK{int ai,dp,val;};
stack<STK> stk;
int dp[N][N], ai[N];
int n;
void Sol()
{
for (int i=0; i<=n; i++)
{
for (int j=0; j<=n; j++)
{
dp[i][j] = INF;
}
}
dp[0][0] = 0;
for (int j=1; j<=n; j++)
{
while (!stk.empty())
stk.pop();
for (int i=j; i<=n; i++)
{
int min_dp = dp[i-1][j-1];
while (!stk.empty() && stk.top().ai <= ai[i])
{
min_dp = min(min_dp, stk.top().dp);
stk.pop();
}
int min_val = INF;
if(!stk.empty())
min_val = stk.top().val;
dp[i][j] = min(min_val, min_dp+ai[i]);
stk.push({ai[i], min_dp, dp[i][j]});
}
}
for (int i=1; i<=n; i++)
{
printf("%lld\n",dp[n][i]);
}
}
signed main()
{
scanf("%lld",&n);
for (int i=1; i<=n; i++)
{
scanf("%lld",&ai[i]);
}
Sol();
return 0;
}