这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
2020-2021:teams:legal_string:jxm2001:莫队算法_2 [2021/02/07 10:29] jxm2001 |
2020-2021:teams:legal_string:jxm2001:莫队算法_2 [2021/08/01 19:50] (当前版本) jxm2001 [算法例题] |
||
---|---|---|---|
行 207: | 行 207: | ||
莫队区间的右端点仍然单增,于是每个块的右端点移动仍可以 $O(n)$ 维护。 | 莫队区间的右端点仍然单增,于是每个块的右端点移动仍可以 $O(n)$ 维护。 | ||
- | 对莫队区间的左端点每次都从 $i+S+1$ 开始重新向左拓展,然后每次处理完询问后再回滚到 $i+S+1$。注意回滚仅回滚左端点不回滚右端点。 | + | 对莫队区间的左端点每次都从 $r+1$ 开始重新向左拓展,然后每次处理完询问后再回滚到 $r+1$。注意回滚仅回滚左端点不回滚右端点。 |
于是每个询问的左端点移动可以 $O(S)$ 维护。总时间复杂仍为 $O\left(ms+\frac {n^2}S\right)$,取 $S=O\left(\frac n{\sqrt m}\right)$,时间复杂度为 $O(n\sqrt m)$。 | 于是每个询问的左端点移动可以 $O(S)$ 维护。总时间复杂仍为 $O\left(ms+\frac {n^2}S\right)$,取 $S=O\left(\frac n{\sqrt m}\right)$,时间复杂度为 $O(n\sqrt m)$。 | ||
- | ==== 算法例题 ==== | + | 另外注意当跑到另一个块时需要消除之前的右端点影响,可以考虑暴力清空(因为只有 $O(S)$ 个块),也可以回滚上一个块的右端点。 |
+ | |||
+ | ==== 例题1 ==== | ||
[[https://loj.ac/p/2874|Loj 2874]] | [[https://loj.ac/p/2874|Loj 2874]] | ||
行 228: | 行 230: | ||
<code cpp> | <code cpp> | ||
const int MAXN=1e5+5; | const int MAXN=1e5+5; | ||
- | int blk_sz,a[MAXN],b[MAXN],col[MAXN],temp[MAXN]; | + | int blk_sz,a[MAXN],b[MAXN],vis[MAXN]; |
struct query{ | struct query{ | ||
int l,r,idx; | int l,r,idx; | ||
行 235: | 行 237: | ||
return r<b.r; | return r<b.r; | ||
} | } | ||
- | }q[MAXN]; | + | }opt[MAXN]; |
- | LL ans[MAXN],cur,tans; | + | LL st[MAXN],cur; |
- | void add(int v,LL &ans){ | + | int tp; |
- | col[v]++; | + | void add(int v){ |
- | ans=max(ans,1LL*col[v]*b[v]); | + | vis[v]++; |
+ | st[++tp]=cur; | ||
+ | cur=max(cur,1LL*vis[v]*b[v]); | ||
} | } | ||
- | void del(int v){col[v]--;} | + | void del(int v){ |
+ | vis[v]--; | ||
+ | cur=st[tp--]; | ||
+ | } | ||
+ | LL ans[MAXN]; | ||
int main() | int main() | ||
{ | { | ||
- | int n=read_int(),qn=read_int(); | + | int n=read_int(),q=read_int(); |
- | blk_sz=n/sqrt(qn)+1; | + | blk_sz=n/sqrt(q)+1; |
_rep(i,1,n)a[i]=b[i]=read_int(); | _rep(i,1,n)a[i]=b[i]=read_int(); | ||
sort(b+1,b+n+1); | sort(b+1,b+n+1); | ||
int m=unique(b+1,b+n+1)-b; | int m=unique(b+1,b+n+1)-b; | ||
_rep(i,1,n)a[i]=lower_bound(b+1,b+m,a[i])-b; | _rep(i,1,n)a[i]=lower_bound(b+1,b+m,a[i])-b; | ||
- | _for(i,0,qn)q[i].l=read_int(),q[i].r=read_int(),q[i].idx=i; | + | _for(i,0,q)opt[i].l=read_int(),opt[i].r=read_int(),opt[i].idx=i; |
- | sort(q,q+qn); | + | sort(opt,opt+q); |
+ | _for(i,0,q){ | ||
+ | if(opt[i].l/blk_sz==opt[i].r/blk_sz){ | ||
+ | _rep(j,opt[i].l,opt[i].r) | ||
+ | add(a[j]); | ||
+ | ans[opt[i].idx]=cur; | ||
+ | for(int j=opt[i].r;j>=opt[i].l;j--) | ||
+ | del(a[j]); | ||
+ | } | ||
+ | } | ||
int lef=1,rig=0,lst=-1; | int lef=1,rig=0,lst=-1; | ||
- | _for(i,0,qn){ | + | _for(i,0,q){ |
- | if(q[i].l/blk_sz==q[i].r/blk_sz){ | + | if(opt[i].l/blk_sz!=opt[i].r/blk_sz){ |
- | tans=0; | + | if(opt[i].l/blk_sz!=lst){ |
- | _rep(j,q[i].l,q[i].r){ | + | while(lef<=rig)del(a[rig--]); |
- | temp[a[j]]++; | + | lst=opt[i].l/blk_sz; |
- | tans=max(tans,1LL*temp[a[j]]*b[a[j]]); | + | rig=min(lst*blk_sz+blk_sz-1,n); |
+ | lef=rig+1; | ||
} | } | ||
- | _rep(j,q[i].l,q[i].r)temp[a[j]]--; | + | while(rig<opt[i].r)add(a[++rig]); |
- | ans[q[i].idx]=tans; | + | int tlef=lef; |
+ | while(tlef>opt[i].l)add(a[--tlef]); | ||
+ | ans[opt[i].idx]=cur; | ||
+ | while(tlef<lef)del(a[tlef++]); | ||
} | } | ||
- | else{ | + | } |
- | if(q[i].l/blk_sz!=lst){ | + | _for(i,0,q)enter(ans[i]); |
- | while(lef<=rig)del(a[lef++]); | + | return 0; |
- | lst=q[i].l/blk_sz; | + | } |
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ==== 例题2 ==== | ||
+ | |||
+ | [[https://ac.nowcoder.com/acm/contest/11256/I|2021牛客暑期多校训练营5 I]] | ||
+ | |||
+ | === 题意 === | ||
+ | |||
+ | 给定一个长度为 $n$ 的序列 $A$,接下来有 $q$ 个询问。每次询问给定 $l,r,k$,求 | ||
+ | |||
+ | $$ | ||
+ | \sum_{i=0}^{k-1}f(l-i,r+i)(n+1)^i | ||
+ | $$ | ||
+ | |||
+ | 其中 $f$ 定义如下 | ||
+ | |||
+ | $$ | ||
+ | f(l,r)=\max(\{k|\exists x\forall i(0\le i\le k-1\to \exists j(l\le j\le r,a_j=x+i))\}) | ||
+ | $$ | ||
+ | |||
+ | |||
+ | === 题解 === | ||
+ | |||
+ | 考虑回滚莫队处理询问,难点在于怎么维护最大的 $k$。 | ||
+ | |||
+ | 实际上,这等价于维护数轴上的最长连续线段。可以令 $\text{vl}(x)$ 表示以数 $x$ 为左端点的线段在数轴上的右端点。 | ||
+ | |||
+ | $\text{vr}(x)$ 表示以数 $x$ 为右端点的线段在数轴上的左端点。于是只需要保证每条线段的两个端点的 $\text{vl},\text{vr}$ 正确性即可,不难 $O(1)$ 维护。 | ||
+ | |||
+ | 时间复杂度 $O\left(n\sqrt q+\sum k\right)$。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=1e5+5,mod=998244353; | ||
+ | int blk_sz,a[MAXN]; | ||
+ | struct query{ | ||
+ | int l,r,k,idx; | ||
+ | bool operator < (const query &b)const{ | ||
+ | if(l/blk_sz!=b.l/blk_sz)return l<b.l; | ||
+ | return r<b.r; | ||
+ | } | ||
+ | }opt[MAXN]; | ||
+ | struct node{ | ||
+ | int p1,v1,p2,v2,ans; | ||
+ | }st[MAXN]; | ||
+ | int curlen,vis[MAXN],vl[MAXN],vr[MAXN],tp; | ||
+ | void add(int v){ | ||
+ | if(vis[v]++)return; | ||
+ | int l=vis[v-1]?vl[v-1]:v; | ||
+ | int r=vis[v+1]?vr[v+1]:v; | ||
+ | st[++tp]=node{l,vr[l],r,vl[r],curlen}; | ||
+ | vr[l]=r; | ||
+ | vl[r]=l; | ||
+ | curlen=max(curlen,r-l+1); | ||
+ | } | ||
+ | void del(int v){ | ||
+ | if(--vis[v])return; | ||
+ | vr[st[tp].p1]=st[tp].v1; | ||
+ | vl[st[tp].p2]=st[tp].v2; | ||
+ | curlen=st[tp].ans; | ||
+ | tp--; | ||
+ | } | ||
+ | int solve(int l,int r,int k,int n){ | ||
+ | int ans=curlen,base=1; | ||
+ | _for(i,1,k){ | ||
+ | add(a[l-i]); | ||
+ | add(a[r+i]); | ||
+ | base=1LL*base*(n+1)%mod; | ||
+ | ans=(ans+1LL*curlen*base)%mod; | ||
+ | } | ||
+ | for(int i=k-1;i;i--){ | ||
+ | del(a[r+i]); | ||
+ | del(a[l-i]); | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | int ans[MAXN]; | ||
+ | int main() | ||
+ | { | ||
+ | int n=read_int(),q=read_int(); | ||
+ | _rep(i,1,n) | ||
+ | a[i]=read_int(); | ||
+ | _for(i,0,q){ | ||
+ | int l=read_int(),r=read_int(),k=read_int(); | ||
+ | opt[i]=query{l,r,k,i}; | ||
+ | } | ||
+ | blk_sz=n/sqrt(q)+1; | ||
+ | sort(opt,opt+q); | ||
+ | _for(i,0,q){ | ||
+ | if(opt[i].l/blk_sz==opt[i].r/blk_sz){ | ||
+ | _rep(j,opt[i].l,opt[i].r) | ||
+ | add(a[j]); | ||
+ | ans[opt[i].idx]=solve(opt[i].l,opt[i].r,opt[i].k,n); | ||
+ | for(int j=opt[i].r;j>=opt[i].l;j--) | ||
+ | del(a[j]); | ||
+ | } | ||
+ | } | ||
+ | int lef=1,rig=0,lst=-1; | ||
+ | _for(i,0,q){ | ||
+ | if(opt[i].l/blk_sz!=opt[i].r/blk_sz){ | ||
+ | if(opt[i].l/blk_sz!=lst){ | ||
+ | while(lef<=rig)del(a[rig--]); | ||
+ | lst=opt[i].l/blk_sz; | ||
rig=min(lst*blk_sz+blk_sz-1,n); | rig=min(lst*blk_sz+blk_sz-1,n); | ||
- | lef=rig+1,cur=0; | + | lef=rig+1; |
} | } | ||
- | while(rig<q[i].r)add(a[++rig],cur); | + | while(rig<opt[i].r)add(a[++rig]); |
- | int tlef=lef;tans=cur; | + | int tlef=lef; |
- | while(tlef>q[i].l)add(a[--tlef],tans); | + | while(tlef>opt[i].l)add(a[--tlef]); |
+ | ans[opt[i].idx]=solve(opt[i].l,opt[i].r,opt[i].k,n); | ||
while(tlef<lef)del(a[tlef++]); | while(tlef<lef)del(a[tlef++]); | ||
- | ans[q[i].idx]=tans; | ||
} | } | ||
} | } | ||
- | _for(i,0,qn)enter(ans[i]); | + | _for(i,0,q)enter(ans[i]); |
return 0; | return 0; | ||
} | } | ||
</code> | </code> | ||
</hidden> | </hidden> | ||
- | |||
===== $\text{bitset}$ 与莫队 ===== | ===== $\text{bitset}$ 与莫队 ===== | ||
行 293: | 行 417: | ||
给定长度为 $n$ 的序列 $a$ 和 $m$ 个询问。 | 给定长度为 $n$ 的序列 $a$ 和 $m$ 个询问。 | ||
- | 每个询问给定 $[l_1,r_1],[l_2,r_2],[l_3,r_3]$,不断删去三个区间中的相同元素,知道三个区间不存在相同元素,问三个区间剩下的数的个数和。 | + | 每个询问给定 $[l_1,r_1],[l_2,r_2],[l_3,r_3]$,不断删去三个区间中的相同元素,直到三个区间不存在相同元素,问三个区间剩下的数的个数和。 |
例如,给定序列 $1,1,1,2,3$,询问区间 $[1,3],[2,4],[3,5]$,进行删去操作后三个区间元素为 $\{1\},\{2\},\{2,3\}$。 | 例如,给定序列 $1,1,1,2,3$,询问区间 $[1,3],[2,4],[3,5]$,进行删去操作后三个区间元素为 $\{1\},\{2\},\{2,3\}$。 |