题解 P6749 【『MdOI R3』Yoshino】

skydogli

2020-08-11 15:29:02

Solution

既然还没加强数据,那我就来写 $O(m(n+v))$ 的题解了。 首先用树状数组 $O(n\log v)$ 算出初始答案以及每个位置前面数值比它大的数量 $cnt_i$ 然后考虑对于每个修改怎么计算贡献。 首先,这个修改内部不产生贡献,所以我们要减去原区间的贡献再加上新区间的贡献即可。 不难发现新的区间的贡献很好计算,对于在这个区间前面的数,令 $tax_i$ 表示数值大于 $i$ 的位置的数量,全部统计一遍,差分后前缀和即可。而对于后面的数,我们只需要讨论它和这个新增的数值区间的关系,具体地,设新区间值域为 $[v_l,v_r]$ ,我们枚举区间后面的数 $a_i$: - $a_i<v_l$ ,贡献为$v_r-v_l+1$ - $v_l\le a_i<v_r$,贡献为$v_r-a_i$ - $a_i\ge v_r$ 贡献为0 这样做的话每次修改是稳定 $n$ 次操作的的。 不过,这样子很难求出原区间的影响,因为原区间值域不一定连续。 但是好心的出题人把值域也给到了 $O(n)$ 级别,也就是我们每次可以对整个值域差分并求前缀和,所以直接像上面一样搞就行了,效率稍差,总体时间复杂度 $O(m(n+v))$ 。 另外,O3对暴力优化极大。 ```cpp #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; #define MN 30005 int mx; int n,m,a[MN],sum[MN],tax[MN],V[MN],fk[MN]; int Op[MN],L[MN],R[MN],X[MN]; void add(int x){ while(x<=mx){ sum[x]++; x+=x&(-x); } } int ask(int x){ int res=0; while(x){ res+=sum[x]; x-=x&(-x); } return res; } int main(){ scanf("%d%d",&n,&m); int res=0; for(int i=1;i<=n;++i){ scanf("%d",&a[i]); mx=max(mx,a[i]); } for(int i=1;i<=n;++i){ V[i]=i-1-ask(a[i]); res+=V[i]; add(a[i]); } for(int i=1;i<=m;++i){ scanf("%d",&Op[i]); if(Op[i]==1){ scanf("%d%d%d",&L[i],&R[i],&X[i]); mx=max(mx,X[i]+R[i]-L[i]); } } for(int i=1;i<=m;++i){ int op=Op[i],l,r,x; if(op==1)l=L[i],r=R[i],x=X[i]; if(op==1){ int vl=x,vr=x+r-l; for(int j=l;j<=r;++j){ tax[j+x-l]=0; fk[a[j]]++; a[j]=j+x-l; res-=V[j]; } tax[vr+1]=0; for(int j=1;j<l;++j){ if(a[j]>vr)tax[vr]++; else if(a[j]>=vl)tax[a[j]-1]++; } for(int j=vr;j>=vl;--j){ tax[j]+=tax[j+1]; V[l+j-vl]=tax[j]; // cerr<<"upd: "<<l+j-vl<<endl; res+=tax[j]; } for(int j=mx;j;--j)fk[j]+=fk[j+1]; for(int j=r+1;j<=n;++j){ res-=fk[a[j]+1]; V[j]-=fk[a[j]+1]; V[j]+=max(0,vr-max(vl-1,a[j])); // cerr<<"?? "<<fk[a[j]+1]<<" "<<max(0,vr-max(vl-1,a[j]))<<endl; res+=max(0,vr-max(vl-1,a[j])); } for(int j=0;j<=mx;++j)fk[j]=tax[j]=0; } else{ printf("%d\n",res); } //if(i==2)break; } return 0; } ```