【POI2008】bzoj1122 账本

234 篇文章 0 订阅
115 篇文章 0 订阅

Description

一个长度为n的记账单,+表示存¥1,-表示取¥1。现在发现记账单有问题。一开始本来已经存了¥p,并且知道最后账户上还有¥q。你要把记账单修改正确,使得
1:账户永远不会出现负数; 2:最后账户上还有¥q。你有2种操作: 1:对某一位取反,耗时x; 2:把最后一位移到第一位,耗时y。
Input

The first line contains 5 integers n, p, q, x and y (1 n 1000000,
0 p;q 1000000, 1 x;y 1000), separated by single spaces and
denoting respectively: the number of transactions done by Byteasar,
initial and final account balance and the number of seconds needed to
perform a single turn (change of sign) and move of transaction to the
beginning. The second line contains a sequence of n signs (each a plus
or a minus), with no spaces in-between. 1 ≤ n ≤ 1000000, 0 ≤ p ,q ≤
1000000, 1 ≤x,y ≤ 1000) Output

修改消耗的时间

首先考虑没有翻转操作。这样的话如果+太多就尽量把后面的+改成-,如果-太多就尽量把前面的-改成+【称为第一类修改】。然后需要考虑的位置就是前缀和最小的位置,如果那里小于零,就需要再把前面的一些-改成+,后面的+改成-【称为第二类修改】,这样是一定可以改过来的,因为只要把之前所有出现过的-都改成+最终的前缀和一定为正。
因为翻转和修改是没有关系的,不妨设先翻转再修改。
把序列变成一个环,枚举起点,也就是枚举翻转的次数。因为不管序列如何排布,第一类修改的方法都是相同的,所以在考虑第一次修改之前求出的最小前缀和,就是上面所说第一类修改完成之后的最小前缀和【不是数值相等,而是位置相同,也就是知道前者可以方便地O(1)知道后者】。用单调队列维护一下,很容易O(n)求出每个起点的最小前缀和。然后枚举起点,按照前面所说进行计算,需要计算的只是第二类修改的次数,这是可以O(1)列式计算的。
第一类修改会让和变化2,这很容易看出。但是,第二类修改同样会让前缀和变化2,这是需要注意的。

#include<cstdio>
#include<cstring>
#define LL long long
const LL oo=1e16;
int a[3000010],n,que[3000010];
LL sum[3000010],f[3000010];
char s[1000010];
LL abs(LL x)
{
    return x>0?x:-x;
}
LL min(LL x,LL y)
{
    return x<y?x:y;
}
int main()
{
    int i,j,k,p,q,hd,tl;
    LL num,temp,ans,u,v,x,y,z;
    scanf("%d%d%d%lld%lld",&n,&p,&q,&x,&y);
    scanf("%s",s+1);
    for (i=1;i<=n;i++)
      a[i]=s[i]=='+'?1:-1;
    for (i=n+1;i<2*n;i++)
      a[i]=a[i-n];
    for (i=1;i<2*n;i++)
      sum[i]=sum[i-1]+a[i];
    hd=1;
    tl=0;
    for (i=2*n-1;i;i--)
    {
        if (hd<=tl&&que[hd]>i+n-1) hd++;
        while (hd<=tl&&sum[que[tl]]>=sum[i]) tl--;
        que[++tl]=i;
        f[i]=sum[que[hd]]-sum[i-1];
    }
    num=(p+sum[n]-q)/2;
    ans=oo;
    for (i=1;i<=n;i++)
    {
        u=i==1?0:n+1-i;
        temp=u*y+abs(num)*x;
        v=num<0?p+f[i]-num*2:p+f[i];
        if (v<0) temp+=2*x*((1-v)/2);
        ans=min(ans,temp);
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值