描述
一个长度为n的记账单,+表示存¥1,-表示取¥1。
现在发现记账单有问题。
一开始本来已经存了¥p,并且知道最后账户上还有¥q。
你要把记账单修改正确,使得
1:账户永远不会出现负数;
2:最后账户上还有¥q。
你有2种操作:
1:对某一位取反,耗时x;
2:把最后一位移到第一位,耗时y。
输入格式
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)
输出格式
修改消耗的时间
测试样例1
输入
9 2 3 2 1
---++++++
输出
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
单调队列+贪心~
因为有一个把最后一位移到第一位的操作,所以用类似于破环为链的方法,重复两次数组,计算前缀和。
维护单调队列,计算每个位置的最小值,记录到数组中,最后枚举移位次数,计算该移位次数下的最小值,更新ans值。
(数组大得好夸张,吓得我都不敢交了……)
(在BZOJ上交要把输出改成lld,否则会PE~)
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define ll long long
int n,p,q,x,y,que[1000100<<1],head,tail,num[1000100<<1];
ll tot,ans,cal[1000100],fi[1000100],now;
char s[1000100];
ll abs(ll u)
{
return u>0 ? u:-u;
}
int main()
{
scanf("%d%d%d%d%d",&n,&p,&q,&x,&y);
scanf("%s",s+1);head=1;tail=0;
for(int i=n*2;i>n;i--) num[i]=num[i+1]+(s[i-n]=='+' ? 1:-1);tot=num[n+1];
for(int i=n;i;i--) num[i]=num[i+1]+(s[i]=='+' ? 1:-1);
for(int i=n*2;i;i--)
{
while(head<=tail && num[i]>num[que[tail]]) tail--;
que[++tail]=i;
while(head<=tail && que[head]-i>n) head++;
if(i<=n) fi[i]=num[i]-num[que[head]];
}
ans=0x7f7f7f7f7f7f7f7fll;
cal[1]=1;
tot=q-p-tot;tot/=2;
for(int i=2;i<=n;i++) cal[i]=n-i+2ll;
for(int i=0;i<n;i++)
{
now=i*y+abs(tot)*x;
fi[cal[i+1]]+=p+max(tot,0ll)*2ll;
if(fi[cal[i+1]]<0) now+=((1-fi[cal[i+1]])/2ll)*2*x;
ans=min(ans,now);
}
printf("%I64d\n",ans);
return 0;
}