题解:P10977 Cut the Sequence
bianshiyang · · 题解
题目传送门
分析
考虑本题解法,首先答案不具有单调性所以二分行不通(并不是所有的最大值最小化都可以用二分),且划分的区间数以及每段区间最大值出现的位置不易得知,故几乎无法使用贪心算法求解。发现如果只考虑最后一段划分的区间,那么其最值只与区间首尾有关,而与前面部分的划分无关,满足最优子结构的性质,考虑使用动态规划进行求解。
定义
发现
我们可以尝试及时排除不必要的决策,首先考虑一个决策
所以对于每一个
所以我们可以利用单调队列维护所有可能成为最优解的决策点 multiset 来做到和单调队列一起删除,插入元素,每次取第一个迭代器就可以快速转移,时间复杂度
代码实现
此题代码有很多细节,有些部分借鉴闫总代码,不好理解的结合代码一起看吧:
- 如何快速找到第一个不满足
\sum_{k=j+1}^ia_k\le m 的j 呢,二分显然可以。但是双指针可以更快,因为i 递增的时候j 一定递增,所以我们用一个变量统计总和即可,并且每次及时删除单调队列中不符合的j 。 -
- 虽然每一个决策
j 都对应一个f_j+\max_{k=j+1}^ia_k ,但是根据第二条可知,每一项的最大值要依赖后一项,也就是说出了单调队列最后一项以外都可以求出对应的f_j+\max_{k=j+1}^ia_k 。而最后一项每次都由新插入的i 来更新即没插入i 前的最后一项在插入i 后就变成了倒数第二项可以更新。而此时有人会疑惑i 又无法更新,但是这样的担心是不必要的,因为i 无法更新f_i ,而下一轮中若i 还存在在单调队列中则会以相同的方式来更新。同理删除元素时第一次删除的即队尾不更新multiset。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+100;
int n,a[N],l=1,r,q[N];
ll m,dp[N],sum;
multiset<ll> st;
void shan(ll x)
{
if(st.find(x)==st.end()) return;//相当于是在这里判断元素存不存在了
st.erase(st.find(x));
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]>m)//不合法的情况
{
printf("-1\n");
return 0;
}
}
for(int i=1,j=0;i<=n;i++)//j就是第一个不满足和小于等于m的位置,详见1
{
sum+=a[i];//j+1到i这一段的和
while(sum>m) sum-=a[++j];
while(l<=r&&q[l]<j) shan(dp[q[l]]+a[q[l+1]]),l++;//及时排除不合法的决策
while(l<=r&&a[q[r]]<=a[i]) shan(dp[q[r-1]]+a[q[r]]),r--;//不满足单调性,这里用r-1就是因为最后一项不删,详见3
if(l<=r) st.insert(dp[q[r]]+a[i]);//队尾用i来更新,详见3
q[++r]=i;
dp[i]=dp[j]+a[q[l]];//可能的决策2
if(st.size()) dp[i]=min(dp[i],*st.begin());//可能的决策1
}
printf("%lld\n",dp[n]);
return 0;
}
完结撒花!!!