题目:BZOJ1233.
题目大意:给定一个长度为
n
n
n的正整数序列
a
a
a,要求把序列分成若干段,使得每一段的权值和大于下一段的权值和,求最多可以分成多少段.
1
≤
n
≤
1
0
5
1\leq n\leq 10^5
1≤n≤105.
这道题首先有一个非常神奇的贪心,就是当分的段最多的时候,必然是让前面的段的和尽量小最好.
这个结论虽然显然,但是并不容易想到利用这个结论DP.
有了这个结论后,考虑正着DP不好办,所以把序列反过来,现在分段的要求变成了每一段的和必须比下一段小.
考虑设 f [ i ] f[i] f[i]表示 a [ 1.. i ] a[1..i] a[1..i]分成若干段之后最后面一段和的最小值, g [ i ] g[i] g[i]表示最大分段数.
由于在 f [ i ] f[i] f[i]最优的时候必然有 g [ i ] g[i] g[i]最优,所以我们可以考虑直接DP出 f [ i ] f[i] f[i]的同时计算出 g [ i ] g[i] g[i].
设
s
[
i
]
s[i]
s[i]为
a
[
i
]
a[i]
a[i]翻转后的前缀和,可以列出方程:
f
[
i
]
=
min
j
<
i
,
f
[
j
]
≤
s
[
i
]
−
s
[
j
]
{
s
[
i
]
−
s
[
j
]
}
f[i]=\min_{j<i,f[j]\leq s[i]-s[j]} \{ s[i]-s[j] \}\\
f[i]=j<i,f[j]≤s[i]−s[j]min{s[i]−s[j]}
设
p
p
p为
f
[
i
]
f[i]
f[i]的转移点,那么:
g
[
i
]
=
g
[
p
]
+
1
g[i]=g[p]+1
g[i]=g[p]+1
然后我们就可以开心快乐的写出一个 O ( n 2 ) O(n^2) O(n2)的DP了,然而会TLE…
现在我们考虑一下如何优化这个转移过程,考虑把方程的转移条件变形:
f
[
j
]
≤
s
[
i
]
−
s
[
j
]
⇔
f
[
j
]
+
s
[
j
]
≤
s
[
i
]
f[j]\leq s[i]-s[j]\Leftrightarrow f[j]+s[j]\leq s[i]
f[j]≤s[i]−s[j]⇔f[j]+s[j]≤s[i]
容易想到 s [ i ] s[i] s[i]是单调递增的,这可以说明当前 j j j合法则接下来 j j j一定合法,而最终转移点又一定是所有合法转移点中最后面的那个,这证明转移点是具有单调性的.
还有一点,我们会发现若 k < j k<j k<j且 f [ k ] + s [ k ] ≥ f [ j ] + s [ j ] f[k]+s[k]\geq f[j]+s[j] f[k]+s[k]≥f[j]+s[j],则显然 k k k合法时 j j j也一定合法,而且 k k k还没有 j j j优,这样就可以及时剔除 k k k了.
容易想到维护一个单调队列,每次若队头以及队列中第二个元素都合法,则将队头出队;并同时维护队尾出队即可.
时间复杂度 O ( n ) O(n) O(n).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000;
int n,a[N+9];
int dp[N+9],cnt[N+9];
int q[N+9],hd,tl;
Abigail into(){
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
}
Abigail work(){
reverse(a+1,a+n+1);
for (int i=1;i<=n;++i) a[i]+=a[i-1];
q[hd=tl=1]=0;
for (int i=1;i<=n;++i){
for (;hd<tl&&dp[q[hd+1]]+a[q[hd+1]]<=a[i];++hd);
dp[i]=a[i]-a[q[hd]];cnt[i]=cnt[q[hd]]+1;
for (;hd<=tl&&dp[q[tl]]+a[q[tl]]>=dp[i]+a[i];--tl);
q[++tl]=i;
}
}
Abigail outo(){
printf("%d\n",cnt[n]);
}
int main(){
into();
work();
outo();
return 0;
}