[CF1988E]笛卡尔树/DS维护 *2300

\[\begin{flalign*} &题意:一个序列长n=5e5,求分别删除第i(1\le i\le n)个数时所有区间最小值之和&\\& 思路1:\\& 若不删数,那么笛卡尔树上dfs一遍即可\\& 若删除了下标u,那么对于u所有祖先来说,一侧子树大小-1\\& 所以答案减少权值*另一侧子树大小\\& 边dfs边统计即可\\& 其次删去u本身的答案\\& 最后根据笛卡尔树的性质,u左儿子的右链,右儿子的左链会重新计算\\& 注意到一条右链只会在链顶作为左儿子被合并一次,一条左链只会在链顶作为右儿子被合并一次\\& 复杂度O(N)\\&\\& 思路2:\\& 考虑一个点i所支配的区间(L_i,R_i),不删数的贡献为(i-L_i)(R_i-i)a_i\\& 删除j\in [1,n]对i的贡献的影响如下:\\& 1.j位于支配区间外,那么i点贡献不变\\& 2.j\in(L_i,i),ans_j-=(R_i-i)a_i\\& 3.j\in(i,R_i),ans_j-=(i-L_i)a_i\\& 4.j=i,ans_j-=(i-L_i)(R_i-i)a_i\\& 假设i左侧第二个小于它的位置为LL_i,右侧为RR_i\\& 5.j=R_i,ans_j+=(i-L_i)(RR_i-R_i-1)a_i(注意这个-1,因为R_i被删除导致的)\\& 6.j=L_i,ans_j+=(R_i-i)(L_i-LL_i-1)a_i\\& LL可以用线段树上二分/二分+st表处理,线性求法不懂\\& 然后枚举i点,用差分统计答案即可\\& \\& 放一个笛卡尔树的代码 \end{flalign*} \]

int a[N],st[N],t[N][2],ans[N],res[N],sz[N];
int top,sum;
void dfs(int u){
	sz[u]=1;
	for(int i=0;i<2;i++){
		if(t[u][i]){
			dfs(t[u][i]); sz[u]+=sz[t[u][i]];
		}
	}
	sum+=(res[u]=(sz[t[u][0]]+1)*(sz[t[u][1]]+1)*a[u]);
}
void dfs2(int u,int val){
	int temp=sum-val-res[u];
	vector<int>tl,tr;
	vector<array<int,2>>hb;
	int y=t[u][0];
	while(y) tl.push_back(y),temp-=res[y],y=t[y][1];
	y=t[u][1];
	while(y) tr.push_back(y),temp-=res[y],y=t[y][0];
	int i=0,j=0;
	while(i<tl.size()||j<tr.size()){
		if(j>=tr.size()||(i<tl.size()&&j<tr.size()&&a[tl[i]]<a[tr[j]])){
			hb.push_back({tl[i],0});
			i++;
		}
		else{
			hb.push_back({tr[j],1});
			j++;
		}
	}
	int cur=0;
	for(int i=hb.size()-1;i>=0;i--){
		auto [p,q]=hb[i];
		temp+=(cur+1)*(sz[t[p][q]]+1)*a[p];
		cur+=sz[t[p][q]]+1;
	}
	ans[u]=temp;
	for(int i=0;i<2;i++){
		if(t[u][i]){
			dfs2(t[u][i],val+(sz[t[u][i^1]]+1)*a[u]);
		}
	}
}
void solve(){
	cin>>n; 
    int root=0;
    for(int i=1;i<=n;i++) cin>>a[i];
	st[top=0]=0;
   	for(int i=1;i<=n;i++) {
		while(top&&a[st[top]]>a[i]) top--;
		t[i][0]=t[st[top]][1],t[st[top]][1]=i;
		st[++top]=i;
	}
	root=st[1];
	sum=0;
	dfs(root);
	dfs2(root,0);
	t[0][0]=t[0][1]=0;
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
		t[i][0]=t[i][1]=0;
	}
	cout<<endl;
}

posted @ 2025-04-01 21:20  肆惠  阅读(30)  评论(0)    收藏  举报