对于一个二元组集合\(\{(a_i,b_i)\}\)
每次可以进行操作
1.如果存在\(a_i=a_j\),可以花费\(b_i\)代价\(a_i\)增加1
2.如果存在\(a_i=a_{j}+1\),可以花费\(-b_i\)代价使\(a_i\)减少1
现在依次向集合插入\(n\)个二元组,求在所有时刻,对于当前的集合进行操作
最终使得不存在\(a_i=a_j\)时的最小花费(可以为负)
容易发现对于给定的\(a_i\)集合,最终\(a_i\)的集合唯一固定
具体的,每次插入一个数值\(x\),如果出现重复就会不停将\(x\)向后推推推
而事实上答案为\(\sum b_i\cdot (a‘_i-a_i)\),那么只需要最小化\(\sum b_ia‘_i\)
容易发现在任意时刻,如果\([L,R]\)内所有\(a_i\)都出现,就可以任意交换他们的\(b_i\)
那么最终状态中每一个\(a_i\)连通块内,按照\(b_i\)从大到小排序即可
每次插入一个元素维护连通块之间的合并以及求出\(\sum b_ia‘_i\)即可
可以用启发式合并/线段树合并维护
const int N=4e5+10,M=N*19,INF=1e9+10;
int n;
int ls[M],rs[M],c[M],cnt;
ll s[M],ans[M];
ll Ans;
int F[N],rt[N];
int Find(int x){ return F[x]==x?x:F[x]=Find(F[x]); }
void Up(int x){
c[x]=c[ls[x]]+c[rs[x]],s[x]=s[ls[x]]+s[rs[x]];
ans[x]=ans[ls[x]]+ans[rs[x]]+c[rs[x]]*s[ls[x]];
}
void Upd(int &p,int l,int r,int x){
if(!p) p=++cnt;
if(l==r) { c[p]=1,s[p]=x; return; }
int mid=(l+r)>>1;
x<=mid?Upd(ls[p],l,mid,x):Upd(rs[p],mid+1,r,x);
Up(p);
}
int Union(int x,int y,int l=1,int r=n){
if(!x||!y) return x|y;
int mid=(l+r)>>1;
ls[x]=Union(ls[x],ls[y],l,mid),rs[x]=Union(rs[x],rs[y],mid+1,r);
return Up(x),x;
}
void Add(int x,int k){
x=Find(x);
Ans+=k*(x*s[rt[x]]+ans[rt[x]]);
}
int main(){
n=rd();
rep(i,1,n){
int x=rd(),y=rd();
Ans-=1ll*x*y;
int f=Find(x);
if(!f) f=F[x]=x;
else Add(f,-1),F[f+c[rt[f]]]=f;
Upd(rt[f],1,n,y);
while(x=Find(x),y=Find(x-1)) {
Add(y,-1);
F[x]=x-1;
rt[y]=Union(rt[y],rt[x]);
}
while(x=Find(x),y=Find(x+c[rt[x]])) {
Add(y,-1);
F[x+c[rt[x]]]=x;
rt[x]=Union(rt[x],rt[y]);
}
Add(x,1),printf("%lld\n",Ans);
}
}
原文:https://www.cnblogs.com/chasedeath/p/14782886.html