【题解】[POI2008] BBB-BBB

该博客探讨了一种涉及序列中+1和-1元素的优化问题。文章提出了两种操作:1) 某一位取反,成本为x,2) 最后一位移动到第一位,成本为y。目标是使得序列的特定前缀和满足特定条件,同时最小化总成本。解决方案包括贪心策略,滑动窗口最小值以及区间操作,时间复杂度为O(n)。
摘要由CSDN通过智能技术生成

题意

给定一个由+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值