bzoj 1233 题解

题意简述

我们有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&lt;j&lt;i)j(0&lt;j&lt;i)j(0<j<i),如果jjjiii的和&gt;=g[j]&gt;=g[j]>=g[j],且iiijjj的和是所有jjj中最小的,那么f[i]=f[j]+1f[i]=f[j]+1f[i]=f[j]+1g[i]=jg[i]=jg[i]=jiii的和。

但显然这个DPDPDP的转移是O(n)O(n)O(n)的,总体就是O(n2)O(n^2)O(n2),会TLETLETLE

2.()优化
通过打表,我们会发现,如果DPDPDP数组倒着推就会有单调性!我们考虑把上面的反一下,也就是f[i]f[i]f[i]表示从nnniii点的最大数量,g[i]g[i]g[i]f[i]f[i]f[i]时的最前面一个排列中的和,转移状态的时候jjj就变成了i&lt;j&lt;=ni&lt;j&lt;=ni<j<=n

设我们刚刚处理的前缀和数组名字叫sss,那么转移方程就变成
if(s(j−1)−s(i−1)&gt;=g[j])[1]f(i)=f(j)+1g(i)=s(j−1)−s(i−1) if(s(j-1)-s(i-1)&gt;=g[j])^{[1]}\\ f(i)=f(j)+1\\ g(i)=s(j-1)-s(i-1)\\ if(s(j1)s(i1)>=g[j])[1]f(i)=f(j)+1g(i)=s(j1)s(i1)
其中jjj取使得s(j−1)−s(i−1)s(j-1)-s(i-1)s(j1)s(i1)最小的那个jjj

我们把[1]^{[1]}[1]式变一下形:
s(j−1)−g[j]&gt;=s(i−1)s(j-1)-g[j]&gt;=s(i-1)s(j1)g[j]>=s(i1)
如果枚举了两个jjj,一个是j1j_1j1,一个是j2j_2j2,令j1&lt;j2j_1&lt;j_2j1<j2,并且s(j2−1)−g(j2)&lt;=s(j1−1)−g(j1)s(j_2-1)-g(j_2)&lt;=s(j_1-1)-g(j_1)s(j21)g(j2)<=s(j11)g(j1)的时候(代码中的判定写作g(j2)−g(j1)&gt;=s(j2−1)−s(j1−1)g(j_2)-g(j_1)&gt;=s(j_2-1)-s(j_1-1)g(j2)g(j1)>=s(j21)s(j11),做了一点移项),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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值