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