[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;
}

浙公网安备 33010602011771号