P1067 象群回响簿 官方题解
【部分分:测试点 1∼41\sim 41∼4】
- 一个显然的思路是对每个询问重新扫描区间 ,统计其中每个数字的出现次数。随后枚举出现过的不同数字 ,检查其出现次数是否恰好为 。
- 使用数组或离散化后的计数表即可完成。总时间复杂度为 ,空间复杂度为 。当询问区间很长且询问数量很多时,重复统计成为瓶颈。
【部分分:测试点 5∼85\sim 85∼8】
- 特殊性质 A 保证 。对每个 ,预处理数字 的出现次数前缀和 。
- 对询问 ,枚举 ,检查 。时间复杂度为 ,空间复杂度为 。
【部分分:测试点 9∼129\sim 129∼12】
- 特殊性质 B 保证每个询问的长度不超过 。仍然使用第一档的直接统计,但只扫描询问区间中的至多 个位置。
- 处理一个询问后,只清空本次出现过的数字,避免每次清空完整值域。总时间复杂度为 ,空间复杂度为 。
【正解】
-
前几档的做法仍然会为不同询问重复统计公共部分。考虑到相邻询问往往有很长的公共区间,我们可以使用莫队调整当前区间。按照左端点所在块排序,同一块内再按照右端点排序。使用奇偶块反向排序右端点可以进一步减少指针移动。
-
令 表示数字 在当前区间中的出现次数,令 表示满足 的不同数字数量。因为区间长度不超过 ,所有 都不可能成为回响数字,可以直接忽略。
-
加入一个值 前,若 ,先令 。随后令 ,若新的 ,再令 。删除一个值时按照完全相同的顺序先撤销旧贡献、修改计数、再加入新贡献。
-
每次指针移动只进行常数次操作,因此块长取 时,单组数据的时间复杂度为 ,空间复杂度为 。多组数据之间只需清空出现过的计数与询问答案。
-
还可以按照询问右端点离线处理。将数字 在前缀 中的出现位置依次记为 ,并令 。若 ,那么区间 中数字 恰好出现 次,当且仅当
- 因此,在右端点 固定时,数字 会对左端点区间
中的每一个位置贡献 。所有数字的贡献相加,就是询问 的答案。
- 扫描到位置 时,只有 的出现次数发生变化。若加入 以前 已经产生贡献,就先把旧的左端点区间减去 ,随后记录新的出现位置,再把新的左端点区间加上 。由于区间长度不超过 , 时可以直接忽略。
- 使用树状数组维护左端点上的差分。对区间 加上 ,只需在 处加上 ,在 处减去 。将询问按照右端点归类,扫描到 后,查询位置 的前缀和即可得到 的答案。
- 每次加入元素至多产生两次区间修改,每个询问只进行一次单点查询。单组数据的时间复杂度为 ,空间复杂度为 。
【参考代码】
#include <cstdio>#include <vector> #define MAXN 100010 using namespace std; int c,T,n,m,Tree[MAXN]; void Add(int Pos,int Val){ for(int i=Pos;i<=n+1;i+=i&-i) Tree[i]+=Val;} int Query(int Pos){ int Ret=0; for(int i=Pos;i;i-=i&-i) Ret+=Tree[i]; return Ret;} void Modify(int l,int r,int Val){ if(l>r) return; Add(l,Val); Add(r+1,-Val);} void Solve(){ scanf("%d%d",&n,&m); vector <int> Line(n+1),Ans(m+1); vector <vector<int> > Pos(n+1); vector <vector<pair<int,int> > > Ask(n+1); for(int i=1;i<=n+1;i++) Tree[i]=0; for(int i=1;i<=n;i++) scanf("%d",&Line[i]); for(int i=1,l,r;i<=m;i++){ scanf("%d%d",&l,&r); Ask[r].push_back(make_pair(l,i)); } for(int r=1;r<=n;r++){ int x=Line[r]; if(x<=n){ int Count=Pos[x].size(); if(Count>=x){ int l=(Count==x?1:Pos[x][Count-x-1]+1); int rr=Pos[x][Count-x]; Modify(l,rr,-1); } Pos[x].push_back(r); Count++; if(Count>=x){ int l=(Count==x?1:Pos[x][Count-x-1]+1); int rr=Pos[x][Count-x]; Modify(l,rr,1); } } for(int i=0;i<(int)Ask[r].size();i++){ Ans[Ask[r][i].second]=Query(Ask[r][i].first); } } for(int i=1;i<=m;i++) printf("%d\n",Ans[i]);} int main(){ scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}