bzoj 3838: [Pa2013]Raper (线段树)

3838: [Pa2013]Raper

Time Limit: 60 Sec   Memory Limit: 128 MB
Submit: 53   Solved: 27
[ Submit][ Status][ Discuss]

Description

你需要生产k张光盘。每张光盘需要经过两道工序:先在A工厂进行挤压,然后送到B工厂涂上反光层。
共有n天时间,你知道每天A,B两家工厂的收费。一家工厂一天只能对一张光盘进行操作。同一天内一张光盘先后在A,B工厂被操作是允许的。你可以假定将只经过A操作的半成品暂存起来不需花费额外代价。
求出生产k张光盘的最小总花费。

Input

第一行两个整数n,k(1<=k<=n<=500000)。
第二行n个整数,表示A工厂每天的收费。
第三行n个整数,表示B工厂每天的收费。(收费均不超过10^9)

Output

输出最小总花费。

Sample Input

8 4
3 8 7 9 9 4 6 8
2 5 9 4 3 8 9 1

Sample Output

32
样例解释:
第一张盘:第1天A,第1天B。
第二张盘:第2天A,第4天B。
第三张盘:第3天A,第5天B。
第四张盘:第6天A,第8天B。

HINT

Source

[ Submit][ Status][ Discuss]

题解:线段树

这道题一直在纠结要不要写题解,表示想了一个上午都没想出来怎么做。最后看的Claris的题解,看了半天才反映过来。。。。

首先我们把a看成左括号,b看成右括号,那么对于一个位置我们如果选择左括号就在这个位置+1,选择右括号就在这个位置-1,那么我们的选择其实就是要形成一个合法的括号序列,使任意位置的前缀和都>=0

对于合法的序列,我们现在要插入一对括号,无非有两种选择(先说明B代表)的位置,A代表(的位置):

(1)插入(),那么对于整个序列来说,[A,B-1]这个区间的前缀和都+1,对于这种插入来说只要两个位置的括号没有使用过就可以插入,也比较好维护

(2)插入)(,那么对于整个序列来说,[B,A-1]这个区间的前缀和都-1,因为一个合法的括号序列满足任意位置的前缀和都>=0,所以[B,A-1]的区间前缀和的最小值>0

那么考虑怎么用线段树维护。。。其实还是挺麻烦的。

va:表示插入一对未使用过的()的最小代价和

vb: 表示插入一对未使用过的)(且满足[B,A-1]的区间最小值大于线段树该结点对应的区间的最小值的最小代价和。

vc: 表示插入一对未使用过的)(的最小代价和

注意va,vb,vc都是结构体,我们虽然是要维护最小代价和,但是因为牵扯到后面的修改,所以我们维护的实际是左右括号的出现位置。vc维护的意义在于合并,因为vc在当前区间可能是不合法的,但是网上合并的时候会出现新的最小值,使vc可以成为大区间满足条件的选择

tr:维护区间前缀和的最小值

delta:维护区间的修改标记

aa:区间a的最小代价,实际维护的还是位置(下同)

ab:区间b的最小代价。

ba:满足[st,A-1]的最小值大于[st,end]的最小值的区间a的最小代价

bb:满足[B,end]的最小值大于[st,end]的最小值的区间b的最小代价

在进行区间合并的时候,我们可以通过组合确定出新的)(的合法选择,具体的做法需要在合并的时候分类讨论。

我们加入A[0]=inf,B[0]=inf,那么区间(0,n]的vb一定是合法的选择,因为任意位置的前缀和和大于等于0嘛。

那么选出的vb必然是>0的啊。

然后每次选出一对括号后就将对应位置的权值改成inf,因为一个括号不能重复使用,然后将包含该位置的所有路径上的值进行重新计算。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 500003
#define inf 1000000003
#define LL long long
using namespace std;
int n,m,k;
int a[N],b[N],aa[N*4],ab[N*4],ba[N*4],bb[N*4],tr[N*4],delta[N*4];
struct data{
	int x,y;
	data(int X=0,int Y=0) {
		x=X,y=Y;
	} 
	data operator +(const data &b1) {
		return (a[x]+b[y]<a[b1.x]+b[b1.y])?(*this):b1;
	}
}va[N*4],vb[N*4],vc[N*4];
void update(int x)
{
	int l=x<<1; int r=x<<1|1;
	va[x]=va[l]+va[r]+data(aa[l],ab[r]);//维护可插入的()最小值的出现位置 
	vc[x]=vc[l]+vc[r]+data(aa[r],ab[l]);//维护)(最小值的出现位置,对于当前区间不一定是合法的插入 
	vb[x]=vb[l]+vb[r];//维护)(最小值,且[B,A-1]的区间最小值大于[st,en]的区间最小值出现位置 
	aa[x]=a[aa[l]]<a[aa[r]]?aa[l]:aa[r];//维护区间中a的最小值 
	ab[x]=b[ab[l]]<b[ab[r]]?ab[l]:ab[r];//维护区间中b的最小值 
	if (tr[l]<tr[r]) {//tr记录的是当前位置的前缀和,对于一个位置插入)相当于-1,插入(相当于+1 
		vb[x]=vb[x]+vc[r]+data(aa[r],bb[l]);
		ba[x]=ba[l];//维护满足[st,A-1]的区间最小值大于[st,en]的区间最小值的代价最小的A的位置 
		bb[x]=b[ab[r]]<b[bb[l]]?ab[r]:bb[l];//维护满足[B,en]的区间最小值大于[st,en]的区间最小值的代价最小的B的位置 
		tr[x]=tr[l];
	}
	if (tr[l]>tr[r]) {
		vb[x]=vb[x]+vc[l]+data(ba[r],ab[l]);
		ba[x]=a[aa[l]]<a[ba[r]]?aa[l]:ba[r];
		bb[x]=bb[r];
		tr[x]=tr[r];
	}
	if (tr[l]==tr[r]) {
		vb[x]=vb[x]+data(ba[r],bb[l]);
		ba[x]=ba[l];
		bb[x]=bb[r];
		tr[x]=tr[l];
	}
}
void build(int now,int l,int r)
{
   if (l==r) {
   	 va[now]=vc[now]=data(l,l); vb[now]=data(0,0);
   	 aa[now]=ab[now]=ba[now]=l;
   	 return;
   }	
   int mid=(l+r)/2;
   build(now<<1,l,mid); 
   build(now<<1|1,mid+1,r);
   update(now);
}
void pushdown(int now)
{
	if (delta[now]) {
		delta[now<<1]+=delta[now];
		delta[now<<1|1]+=delta[now];
		tr[now<<1]+=delta[now];
		tr[now<<1|1]+=delta[now];
		delta[now]=0;
	}
}
void qjchange(int now,int l,int r,int ll,int rr,int val)
{
	if (ll<=l&&r<=rr) {
		tr[now]+=val;
		delta[now]+=val;
		return;
	}
	pushdown(now);
	int mid=(l+r)/2;
	if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,val);
	if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,val);
    update(now);
}
void pointchange(int now,int l,int r,int x)
{
	if (l==r) return;
	pushdown(now);
	int mid=(l+r)/2;
	if (x<=mid) pointchange(now<<1,l,mid,x);
	else pointchange(now<<1|1,mid+1,r,x);
	update(now);
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++) scanf("%d",&b[i]);
	a[0]=b[0]=inf;
	build(1,0,n); LL ans=0;
	for (int i=1;i<=k;i++) {
		data t=va[1]+vb[1];
		ans+=(LL)a[t.x]+b[t.y];
		if (t.x<t.y) qjchange(1,0,n,t.x,t.y-1,1);
		if (t.x>t.y) qjchange(1,0,n,t.y,t.x-1,-1);
		a[t.x]=inf; b[t.y]=inf;
		pointchange(1,0,n,t.x); pointchange(1,0,n,t.y);
	}
	printf("%I64d\n",ans);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值