解题:POI 2008 BBB----单调队列,贪心
题面
题外话:发现自己平时调题老下数据现在没数据不会调题了,得改改了(我不会告诉你我因为少加了个括号又乱调了
先想想最直接的做法?
可以发现取反一个数会导致
取反次数
那要求
然后枚举移动操作,具体就是枚举最终的序列以哪个点开始,直接复制一份来做就吼了。枚举每个起点(当然,对于初始序列来说,不管起点怎么选反转次数都是一定的),然后计算移动次数。这里当然是不能
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int num[2000009],fsum[2000009],cnt[2000009];//cnt[i]:以i为起点的前缀和最小值
char rd[1000009];
int n,p,q,c1,c2,minc,op,ap;
deque<int> qs;
int more(int ine)//一个计算函数
{
int tmp=p+cnt[ine];//计算次数中...
if(op<0) tmp+=2*ap;//初始fsum[n]<=q的情况
return 2*((tmp<0)?(1-tmp)/2:0)+ap;//注意除法时的取整
}
int main ()
{
scanf("%d%d%d%d%d%s",&n,&p,&q,&c1,&c2,rd);
for(int i=0;i<n;i++)
num[i+1+n]=num[i+1]=(rd[i]=='+')?1:-1;
for(int i=1;i<=2*n;i++)
fsum[i]=fsum[i-1]+num[i];
for(int i=2*n;i;i--)//单调队列维护前缀和最小值(下标)
{
while(!qs.empty()&&fsum[qs.back()]>=fsum[i])
qs.pop_back();//大的丢掉
qs.push_back(i);//当前的进来
while(!qs.empty()&&qs.front()>i+n)
qs.pop_front();//找到右端点
cnt[i]=fsum[qs.front()]-fsum[i-1];
}
op=(fsum[n]+p-q)/2,ap=abs(op),minc=more(1)*c1;//op:初始序列的反转操作次数,ap:计算用,常数小233,minc:答案
for(int i=2;i<=n;i++)
minc=min(minc,(n+1-i)*c2+more(i)*c1);//位置差(移动次数)*移动代价+反转次数*反转代价更新
printf("%d",minc);
return 0;
}