【JZOJ 3083】塔(加强)

本文介绍了一种通过优化动态规划解决塔合并游戏问题的方法。游戏的目标是将一系列塔进行合并,使得最终形成的塔高度不下降,且操作次数最少。文章详细解释了如何利用单调队列来优化算法的时间复杂度。

Description

玩完骰子游戏之后,你已经不满足于骰子游戏了,你要玩更高级的游戏。

   今天你瞄准了下述的好玩的游戏:


   首先是主角:塔。你有N座塔一列排开。每座塔各自有高度,有可能相等。


   这个游戏就不需要地图了。


   你每次可以选择相邻的两座塔合并在一起,即这两座塔的高度叠加后变成了同一座塔。然后原本分别与这两座塔相邻的塔变得与这座新的塔相邻。


   你的目标是在使用最少的操作次数在游戏的最后获得一列塔,这些塔的高度从左到右形成一个不下降的数列。

 

抽象题意:给出一个序列,把这个序列分块,使得每块的和不下降,每块的代价为每个块内元素个数-1,求最小的代价。

Solution

很显然有两个性质:
性质1:分的块越多,最后的块的大小就越小,
性质2:块的大小越小,分的块就越多,
现要使分的块尽量多,
根据性质1,可以写出DP使:

fi=min(fj(FjAiAj)+ij1)

f为到j为止分的块数,F为以j为结尾的块的和,A为前缀和,
复杂度:O(n2)
发现过不了,
继续优化,
根据性质2,可以发现,一旦找到一个合法的j,就不用继续找下去了,
FjAiAj

移项:
Fj+AjAi

所有可以根据这个,用一个单调队列,(本题的单调队列绝对是一个异类!!!
队头是合法的,从第二个开始不合法,
随着i的增大,第二个迟早会合法的,如果第二个合法了,就把第一个删掉,
复杂度:O(n)

Code

#include<cstdio>
#include<cstdlib>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long LL;
const int N=1000500;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,ans,M,FAIL;
int a[N];
LL f[N][2],A[N];
int qu[N];
int main()
{
    int l,r;
    read(n);
    fo(i,1,n)read(a[i]),A[i]=A[i-1]+a[i];
    qu[l=r=1]=0;
    fo(i,1,n)
    {
        while(f[qu[l+1]][1]+A[qu[l+1]]<=A[i]&&l<r)l++;
        f[i][0]=f[qu[l]][0]+i-qu[l]-1;
        f[i][1]=A[i]-A[qu[l]];
        while(l<=r&&f[qu[r]][1]+A[qu[r]]>=f[i][1]+A[i])r--;
        qu[++r]=i;
    }
    printf("%d\n",f[n][0]);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值