CF1051G - Distinctification

CF1051G - Distinctification

题目大意

对于一个二元组集合\(\{(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);
	}
}
posted @ 2021-05-18 21:47  chasedeath  阅读(110)  评论(0编辑  收藏  举报