题解:线段树
这道题一直在纠结要不要写题解,表示想了一个上午都没想出来怎么做。最后看的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
#include
#include
#include
#include
#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]tr[r]) {
vb[x]=vb[x]+vc[l]+data(ba[r],ab[l]);
ba[x]=a[aa[l]]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.xt.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);
}