CF1824D LuoTianyi and the Function 推标记 区间历史版本和

· · 题解

考虑去拆 \displaystyle \sum_{i=l}^{r}\sum_{j=x}^{y}g(i,j) 的贡献:

\displaystyle \sum_{i=l}^{r}\sum_{j=x}^{y}g(i,j)=\sum_{i=l}^{r}\sum_{j=1}^{y}g(i,j)-\sum_{i=l}^{r}\sum_{j=1}^{x-1}g(i,j)

题目告诉我们当 i>jg(i,j) 的贡献为 0,所以我们可以进一步把式子写成:

\sum_{i=l}^{min(r,y)}\sum_{j=1}^{y}g(i,j)-\sum_{i=l}^{min(r,x-1)}\;\sum_{j=1}^{x-1}g(i,j)

这是一个历史版本和问题,多组询问离线用扫描线维护。

F_i 为在 i 位置(包括 i 位置)之后 a_{i} 出现的次数。

假设我们现在处理到 jg(i,j) 的值为 i 之后第一个出现 F_x=1 的位置。

它影响的区间是从 x 的位置之前再一个 F_y=1 的位置的后一个位置到 x

(区间)即 y+1\sim x

我们从左向右依次加入 a_{i},每次加入看作一个新的版本。

如果 a_{i} 在之前出现过,我们考虑对其影响:

此时局面

加入 a_{i} 后,F_{pre_{a_{i}}} 就不是 1 了。

我们修改贡献,得:

发现这是一个区间覆盖。

加入其贡献后,再使线段树上 1\sim i 区间维护的当前版本加入历史版本和。

这是一个区间加。

所以我们需要一颗区间覆盖区间加线段树。

考虑推标记(懒可以直接矩阵,就是可能要卡常或者拆,我太菜了不会)

大佬 Ckain 的矩阵版:blog

一共维护 3 个标记:

因为 $tga$ 是依赖于当前版本,所以它的优先级应该高于 $tgcv$。 将每个节点的标记看作一个队列,同种可合并,但是相隔 $tgcv$ 的两个 $tga$ 并不可以合并,原因就是刚刚说的,$tga$ 是依赖于当前版本,$tgcv$ 更改了当前版本,两个 $tga$ 依赖的不是同一个版本,但是在 $tgcv$ 后的 $tga$ 会退化成 $tgc$(可直接算出,直接变成常数。 可以结合以下代码理解: ```cpp void addx(int p,int x){//加法标记 t[p].hd+=x*t[p].d; if(t[p].tg.cx)t[p].tg.c1+=x*t[p].tg.cx; else t[p].tg.addx+=x; } void addc(int p,int x){//常数标记 t[p].hd+=x*t[p].len; t[p].tg.c1+=x; } void cx(int p,int x){//覆盖标记 t[p].d=x*t[p].len; t[p].tg.cx=x; } void pd(int p){ if(t[p].tg.addx)addx(ls,t[p].tg.addx),addx(rs,t[p].tg.addx); if(t[p].tg.c1)addc(ls,t[p].tg.c1),addc(rs,t[p].tg.c1); if(t[p].tg.cx)cx(ls,t[p].tg.cx),cx(rs,t[p].tg.cx); t[p].tg={0,0,0}; } ``` 还有细节问题可参考代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; inline int rd(void){ int s=0, f=1; char c=getchar(); while(c<'0' || c>'9') {if(c=='-') f=0; c=getchar();} while(c>='0' && c<='9') {s=s*10+c-'0'; c=getchar();} return f? s:-s; } const int N=1e6+10; int n,m; int a[N]; struct tag{ int addx,cx,c1; }; struct node{ tag tg; int hd,d,len,l,r; }t[N<<2]; #define ls p*2 #define rs p*2+1 #define mid (t[p].l+t[p].r)/2 void build(int p,int l,int r){ t[p].l=l;t[p].r=r;t[p].len=(r-l+1); if(l==r)return ; build(ls,l,mid); build(rs,mid+1,r); } void addx(int p,int x){//加法标记 t[p].hd+=x*t[p].d; if(t[p].tg.cx)t[p].tg.c1+=x*t[p].tg.cx; else t[p].tg.addx+=x; } void addc(int p,int x){//常数标记 t[p].hd+=x*t[p].len; t[p].tg.c1+=x; } void cx(int p,int x){//覆盖标记 t[p].d=x*t[p].len; t[p].tg.cx=x; } void pd(int p){ if(t[p].tg.addx)addx(ls,t[p].tg.addx),addx(rs,t[p].tg.addx); if(t[p].tg.c1)addc(ls,t[p].tg.c1),addc(rs,t[p].tg.c1); if(t[p].tg.cx)cx(ls,t[p].tg.cx),cx(rs,t[p].tg.cx); t[p].tg={0,0,0}; } void pu(int p){ t[p].hd=t[ls].hd+t[rs].hd; t[p].d=t[ls].d+t[rs].d; } void upd(int p,int l,int r,int v,int f){ //if(l>r)return ; if(t[p].l==l&&t[p].r==r){ if(f)addx(p,v); else cx(p,v); return ; } pd(p); if(r<=mid)upd(ls,l,r,v,f); else if(l>mid)upd(rs,l,r,v,f); else{ upd(ls,l,mid,v,f);upd(rs,mid+1,r,v,f); } pu(p); } int query(int p,int l,int r){ //if(l>r)return 0; if(t[p].l==l&&t[p].r==r){ return t[p].hd; } pd(p); if(r<=mid)return query(ls,l,r); else if(l>mid)return query(rs,l,r); else{ return query(ls,l,mid)+query(rs,mid+1,r); } } #undef ls #undef rs #undef mid struct Que{ int l,r,f,id; }; vector<Que> que[N]; set<int> st; int pre[N],ans[N],pos[N]; signed main(){ n=rd();m=rd(); for(int i=1;i<=n;i++)a[i]=rd(); for(int i=1;i<=m;i++){ int l=rd(),r=rd(),x=rd(),y=rd(); que[y].push_back({l,min(y,r),1,i}); que[x-1].push_back({l,min(x-1,r),-1,i}); } build(1,1,n); st.insert(0); for(int i=1;i<=n;i++){ if(pre[a[i]]){ auto it=st.lower_bound(pre[a[i]]); int l=*(--it);++it; int v=((++it)==st.end())?i:*it;--it; upd(1,l+1,pre[a[i]],v,0); st.erase(it); } pre[a[i]]=i; st.insert(i); upd(1,i,i,i,0); upd(1,1,i,1,1); for(Que k:que[i]){ if(k.l<=k.r)ans[k.id]+=k.f*query(1,k.l,k.r); } } for(int i=1;i<=m;i++){ printf("%lld\n",ans[i]); } return 0; } ```