题意简述
我们有nnn个数,要把这些数连续的划分,使得这些划分中的和不下降。输出最大能划分成多少个。
数据
输入
3
1
2
3
输出
2
解释:
划分:∣(1,2)∣(3)∣|(1,2)|(3)|∣(1,2)∣(3)∣(其中一个∣|∣表示切开)
也就是1,2在一块,3在一块即可
思路
一看就知道是个DP。
如何推+优化转移方程呢?
1.(老汉)推(车)
首先肯定要有一个fff表示最大数量,毕竟这是答案。设f[i]f[i]f[i]表示到iii点的最大数量,那么答案就是f[n]f[n]f[n]。在我们转移的过程中,我们会发现,这个划分还要单调不增,所以还要加上限制。如何加上这个毒瘤的限制呢?考虑多开一个ggg(是指一个ggg数组,不是一个GGG的内存。。),g[i]g[i]g[i]表示f[i]f[i]f[i]这个状态的最后一个划分中的和。为了方便求和,预处理好前缀和。我们在计算f[i]f[i]f[i]的时候,枚举一个j(0<j<i)j(0<j<i)j(0<j<i),如果jjj到iii的和>=g[j]>=g[j]>=g[j],且iii到jjj的和是所有jjj中最小的,那么f[i]=f[j]+1f[i]=f[j]+1f[i]=f[j]+1,g[i]=jg[i]=jg[i]=j到iii的和。
但显然这个DPDPDP的转移是O(n)O(n)O(n)的,总体就是O(n2)O(n^2)O(n2),会TLETLETLE。
2.(女)优化
通过打表,我们会发现,如果DPDPDP数组倒着推就会有单调性!我们考虑把上面的反一下,也就是f[i]f[i]f[i]表示从nnn到iii点的最大数量,g[i]g[i]g[i]是f[i]f[i]f[i]时的最前面一个排列中的和,转移状态的时候jjj就变成了i<j<=ni<j<=ni<j<=n。
设我们刚刚处理的前缀和数组名字叫sss,那么转移方程就变成
if(s(j−1)−s(i−1)>=g[j])[1]f(i)=f(j)+1g(i)=s(j−1)−s(i−1)
if(s(j-1)-s(i-1)>=g[j])^{[1]}\\
f(i)=f(j)+1\\
g(i)=s(j-1)-s(i-1)\\
if(s(j−1)−s(i−1)>=g[j])[1]f(i)=f(j)+1g(i)=s(j−1)−s(i−1)
其中jjj取使得s(j−1)−s(i−1)s(j-1)-s(i-1)s(j−1)−s(i−1)最小的那个jjj。
我们把[1]^{[1]}[1]式变一下形:
s(j−1)−g[j]>=s(i−1)s(j-1)-g[j]>=s(i-1)s(j−1)−g[j]>=s(i−1)
如果枚举了两个jjj,一个是j1j_1j1,一个是j2j_2j2,令j1<j2j_1<j_2j1<j2,并且s(j2−1)−g(j2)<=s(j1−1)−g(j1)s(j_2-1)-g(j_2)<=s(j_1-1)-g(j_1)s(j2−1)−g(j2)<=s(j1−1)−g(j1)的时候(代码中的判定写作g(j2)−g(j1)>=s(j2−1)−s(j1−1)g(j_2)-g(j_1)>=s(j_2-1)-s(j_1-1)g(j2)−g(j1)>=s(j2−1)−s(j1−1),做了一点移项),j2j_2j2就相当于 被阉了 废了。
如何把这些 太监没用的j2j_2j2给去掉呢?
单调队列。
然后这个题就非常优美的O(n)O(n)O(n)的过了。
代码:
#include<bits/stdc++.h>
#define N 100100
using namespace std;
int a[N],n;
int s[N];//前缀和数组
void Input()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
}
int Q[N],head,tail;//单调队列
int g[N],f[N];//和上面一样的
int sum(int l,int r)//求l到r的和
{
return s[r]-s[l-1];
}
void Solve()
{
head=tail=1;//这个队列是初始1,1然后队列包括tail这个位置的写法
Q[1]=n+1;
for(int i=n;i>=1;i--)
{
#define Front (Q[head])//队首
#define Back (Q[tail])//队尾
while(head<tail and sum(i,Q[head+1]-1)>=g[Q[head+1]]) ++head;//去掉队首的不合法的值
g[i]=sum(i,Front-1);
f[i]=f[Front]+1;
while(head<=tail and sum(i,Back-1)<=g[Back]-g[i]) --tail;//去掉队尾的没用值
Q[++tail]=i;//加入队列
}
printf("%d\n",f[1]);//f[1]是答案
}
main()
{
Input();
Solve();
return 0;
}
1155

被折叠的 条评论
为什么被折叠?



