一类双排列区间操作问题
基础版本
给出一个长度为 \(n\) 的排列 \(p_i\),进行 \(m\) 次区间操作,要么 \(\forall l\leq i\leq r, a_i\leftarrow a_i+w\),要么求 \(\sum_{i=l}^{r}a_{p_i}\)。
做法
分块,设块长为 \(B\)。
散块对散块和整块对散块是简单的,复杂度为 \(O(m\times \frac{n}{B}+mB)\)。
整块对整块,可以预处理 \(p_i\) 的前缀整块在 \(id_i\) 的前缀整块的出现次数,差分即可,复杂度为 \(O((\frac{n}{B})^2\times B+m\times \frac{n}{B})\)。
散块对整块,暴力找逆位置所在的块,求前缀和后更新,复杂度为 \(O(mB+m\times \frac{n}{B})\)。
取 \(B=\sqrt{n}\) 平衡得到时间复杂度为 \(O((n+m)\sqrt{n})\)。
空间复杂度在于维护两个前缀,为 \(O(B^2)=O(n)\)。
2024-2025 集训队互测 Round 2 (Oct 19, 2024) B
树剖后就是基础版本,时间复杂度为 \(O((n+m)\sqrt{n}\log n)\),空间复杂度为 \(O(n)\)。
存在时间复杂度为 \(O((n+m)\sqrt n)\) 的做法。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=2e5+5;
const int B=450;
// const int B=2;
int n, m;
struct DS{
int p[N], revp[N];
int pos[N], lp[B], rp[B];
int cnt[B][B];
vector<int> occ[N];
ull atag[B], aself[N];
ull sum[B], dt[B];
inline ull qryp(int x){
return atag[pos[x]]+aself[x];
}
inline ull qry(int x){
if(x==0) return 0;
ull ret=sum[pos[x]-1];
for(int i=lp[pos[x]]; i<=x; ++i){
ret+=qryp(p[i]);
}
return ret;
}
void init(){
for(int i=1; i<=n; ++i) pos[i]=(i-1)/B+1;
for(int i=1; i<=pos[n]; ++i) lp[i]=rp[i-1]+1, rp[i]=rp[i-1]+B;
rp[pos[n]]=n;
for(int i=1; i<=pos[n]; ++i){
for(int j=1; j<=pos[n]; ++j){
cnt[i][j]=cnt[i][j-1];
for(int k=lp[i]; k<=rp[i]; ++k){
cnt[i][j]+=(p[k]>=lp[j]&&p[k]<=rp[j]);
}
}
}
for(int i=1; i<=pos[n]; ++i){
for(int j=1; j<=pos[n]; ++j){
cnt[i][j]+=cnt[i-1][j];
}
}
}
void mdf(int l, int r, ull x){
if(pos[l]==pos[r]){
for(int i=l; i<=r; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
}
else{
for(int i=l; i<=rp[pos[l]]; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
for(int i=lp[pos[r]]; i<=r; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
for(int i=pos[l]+1; i<pos[r]; ++i) atag[i]+=x;
for(int i=1; i<=pos[n]; ++i) {
sum[i]+=x*(cnt[i][pos[r]-1]-cnt[i][pos[l]]);
}
}
}
}D;
ull a[N];
struct Tree{
vector<int> e[N];
int sz[N], son[N], dfn[N], timer, top[N], f[N], dep[N];
void dfs1(int x, int fa){
f[x]=fa; sz[x]=1; dep[x]=dep[fa]+1;
for(auto y:e[x]) if(y^fa) {
dfs1(y, x);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
void dfs2(int x, int tp){
top[x]=tp; dfn[x]=++timer;
if(son[x]) dfs2(son[x], tp);
for(auto y:e[x]) if(y!=f[x]&&y!=son[x]){
dfs2(y, y);
}
}
vector<pair<int, int> > split(int x, int y){
vector<pair<int, int> > ret;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x, y);
ret.push_back(make_pair(dfn[top[x]], dfn[x]));
x=f[top[x]];
}
if(dfn[x]>dfn[y]) swap(x, y);
ret.push_back(make_pair(dfn[x], dfn[y]));
return ret;
}
}t1, t2;
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i) scanf("%llu", &a[i]);
for(int i=1, x, y; i<n; ++i){
scanf("%d%d", &x, &y);
t1.e[x].push_back(y);
t1.e[y].push_back(x);
}
for(int i=1, x, y; i<n; ++i){
scanf("%d%d", &x, &y);
t2.e[x].push_back(y);
t2.e[y].push_back(x);
}
t1.dfs1(1, 0); t1.dfs2(1, 1);
t2.dfs1(1, 0); t2.dfs2(1, 1);
for(int i=1; i<=n; ++i){
D.p[t2.dfn[i]]=t1.dfn[i];
D.revp[t1.dfn[i]]=t2.dfn[i];
}
D.init();
for(int i=1; i<=n; ++i){
D.sum[D.pos[t2.dfn[i]]]+=a[i];
D.aself[t1.dfn[i]]=a[i];
}
for(int i=1; i<=D.pos[n]; ++i) D.sum[i]+=D.sum[i-1];
int l, r; ull x;
while(m--){
scanf("%d%d%llu", &l, &r, &x);
vector<pair<int, int> > v1=t1.split(l, r);
for(auto t:v1){
D.mdf(t.first, t.second, x);
}
for(int i=1; i<=D.pos[n]; ++i) {
D.dt[i+1]+=D.dt[i];
D.sum[i]+=x*D.dt[i];
D.dt[i]=0;
}
ull ans=0;
vector<pair<int, int> > v2=t2.split(l, r);
for(auto t:v2){
ans+=D.qry(t.second)-D.qry(t.first-1);
}
printf("%llu\n", ans);
}
}
2025牛客暑期多校训练营6 E
给出的不是排列,但实际上可以改造成排列。
具体的,删除没出现的数,把每个数复制(出现次数)次,就变成了基础版本。
时间复杂度为 \(O((n+m)\sqrt{n})\),空间复杂度为 \(O(n)\)。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int B=350;
// const int B=2;
int n, m;
int p[N], revp[N];
int cl[N], cr[N];
int pos[N], lp[350], rp[350];
int cnt[350][350];
vector<int> occ[N];
typedef long long ll;
ll atag[350], aself[N];
ll sum[350], dt[350];
inline ll qryp(int x){
return atag[pos[x]]+aself[x];
}
inline ll qry(int x){
if(x==0) return 0;
ll ret=sum[pos[x]-1];
for(int i=lp[pos[x]]; i<=x; ++i){
ret+=qryp(p[i]);
}
return ret;
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i) scanf("%d", &p[i]), occ[p[i]].push_back(i);
for(int i=1; i<=n; ++i){
cl[i]=cr[i-1]+1;
cr[i]=cr[i-1];
for(auto t:occ[i]){
++cr[i];
p[t]=cr[i];
revp[cr[i]]=t;
}
}
for(int i=1; i<=n; ++i) pos[i]=(i-1)/B+1;
for(int i=1; i<=pos[n]; ++i) lp[i]=rp[i-1]+1, rp[i]=rp[i-1]+B;
rp[pos[n]]=n;
for(int i=1; i<=pos[n]; ++i){
for(int j=1; j<=pos[n]; ++j){
cnt[i][j]=cnt[i][j-1];
for(int k=lp[i]; k<=rp[i]; ++k){
cnt[i][j]+=(p[k]>=lp[j]&&p[k]<=rp[j]);
}
}
}
for(int i=1; i<=pos[n]; ++i){
for(int j=1; j<=pos[n]; ++j){
cnt[i][j]+=cnt[i-1][j];
}
}
ll lstans=0;
int tp; ll l, r, x;
while(m--){
scanf("%d%lld%lld", &tp, &l, &r);
l^=lstans; r^=lstans;
if(tp==1){
scanf("%lld", &x);
x^=lstans;
l=cl[l]; r=cr[r];
if(l>r) continue;
if(pos[l]==pos[r]){
for(int i=l; i<=r; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
}
else{
for(int i=l; i<=rp[pos[l]]; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
for(int i=lp[pos[r]]; i<=r; ++i) aself[i]+=x, dt[pos[revp[i]]]++;
for(int i=pos[l]+1; i<pos[r]; ++i) atag[i]+=x;
for(int i=1; i<=pos[n]; ++i) {
sum[i]+=x*(cnt[i][pos[r]-1]-cnt[i][pos[l]]);
}
}
for(int i=1; i<=pos[n]; ++i) {
dt[i+1]+=dt[i];
sum[i]+=x*dt[i];
dt[i]=0;
}
}
else{
lstans=qry(r)-qry(l-1);
printf("%lld\n", lstans);
}
}
}