题意
给定一个由+1和−1构成的长度为n的序列,提供两种操作:
1.将某一位取反,花销为x
2.将最后一位移动到第一位,花销为y
要求最终p+sumn=q,且p+sumi≥0(1≤i≤n),求最小花销
sumi为1-i的前缀和
Solution:
考点:贪心+分析性质。
首先考虑将后面一段接到前面,容易想到 O(n)
枚举分割点。
然后 p+sum[i]>=0
的话,找到 minsum[i]
,每次 +2
直到 minsum[i] >= -p
。最后算一下 abs(sum[n]-q)/2
求偏移量即可。(每次贪心地在序列尾部修改应该没什么问题吧)。
问题转化为 滑动区间求最小值
,可以用单调队列维护。具体地,首先剖成 2*n
长度的链,然后维护整体偏移量 delta
即可。
时间复杂度 O(n)
。有更简洁的实现方法,可以不用单调队列,参见
k
c
z
n
o
1
kczno1
kczno1 的博客。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define PII pair<int,int>
using namespace std;
const int mx=2e6+5;
int n,p,Q,A,B,head,tail,q[mx],sum[mx],res[mx],b[mx],delta;
ll ans(-1);
char a[mx];
int main() {
scanf("%d%d%d%d%d%s",&n,&p,&Q,&A,&B,a+1);
int tmp(p);
for(int i=1;i<=n;i++) {
if(a[i]=='+') tmp++;
else tmp--;
}
//首先考虑将后面一段接到前面
//可以 O(n) 枚举 , 然后从前到后扫一遍 ,每次把一个 '-' 换成 '+' ,最后再从后往前把它替换回来
//这个性质相当于求出 minsum_i ,每次 +2 直到 minsum_i >= -p
a[n+1]=a[1];
for(int i=1;i<=n;i++) {
a[i]=a[i+1];
b[i]=(a[i]=='+')?1:-1;
b[i+n]=b[i];
}
head=1,tail=0;
for(int i=1;i<2*n;i++) {
if(i>=n) delta+=b[i-n];
sum[i]=sum[i-1]+b[i];
res[i]=delta+(i<=n)?sum[i]:sum[i]-sum[i-n];
while(head<=tail&&q[head]<=i-n) head++;
while(head<=tail&&res[q[tail]]>=res[i]) tail--;
q[++tail]=i;
if(i>=n) {
ll Min=res[q[head]]-delta,cost=1ll*(2*n-i-1)*B,stp;
if(Min<-p) stp=(-p-Min+1)/2,cost+=1ll*stp*A,tmp+=stp*2;
cost+=1ll*(abs(tmp-Q)+1)/2*A;
if(Min<-p) tmp-=stp*2;
if(ans==-1||cost<ans) ans=cost;
}
}
printf("%lld",ans);
}
更简洁的做法:
#include<bits/stdc++.h>
using namespace std;
template <typename T> void chmin(T &x,const T &y)
{
if(x>y)x=y;
}
#define rep(i,l,r) for(int i=l;i<=r;++i)
const int N=1e6+5,inf=2e9;
char s[N];
int a[N],mn_a[N],b[N];
int main()
{
int n,p,q,x,y;
cin>>n>>p>>q>>x>>y;
scanf("%s",s+1);
rep(i,1,n)s[i]=s[i]=='-'?-1:1;
rep(i,1,n)a[i]=a[i-1]+s[i];
rep(i,1,n)mn_a[i]=min(mn_a[i-1],a[i]);
int d=(q-p-a[n])/2;
int ans=inf;
rep(i,1,n)
{
int n_mn=min(a[n]-a[n-i+1]+mn_a[n-i+1],b[i-1]);
int now=max(0,(-p-n_mn+1)/2);
chmin(ans,(i-1)*y+(now+abs(d-now))*x);
b[i]=min(b[i-1]+s[n-i+1],0);
}
cout<<ans;
}