sqrt数据结构
简单题:弹飞绵羊
分块:
将序列分块,每块sqrt(n)个。
在每个块中维护f[i],to[i]
f[i] 表示跳几次可以跳出所在块
to[i] 表示跳出所在块后到达的位置。
在查询时,我们O(sqrt(n))的时间进行“整块”的模拟,可以得到结果。
在修改i时,我们只需维护一下(l[belong[i]]--i)的序列就可以了。
总复杂度:O(n*sqrt(n))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define maxn 200002 4 #define N 500 5 int to[maxn],f[maxn],n,R[500],L[500],val[maxn],pos[maxn]; 6 //能弹到哪儿,弹了几次 7 8 int query(int st) 9 { 10 int ans=0; 11 while(st<=n) 12 { 13 ans+=f[st]; 14 // cout<<f[st]<<endl; 15 st=to[st]; 16 // cout<<st<<endl; 17 } 18 return ans; 19 } 20 21 void change(int x)//对改的点所在的块暴力重构 22 { 23 for(int j=R[x];j>=L[x];j--)//倒序循环因为修改了的点会对前面的点有影响 24 { 25 if(j+val[j]<=R[x]) f[j]=f[j+val[j]]+1,to[j]=to[j+val[j]]; 26 else f[j]=1,to[j]=j+val[j]; 27 } 28 } 29 30 int main() 31 { 32 scanf("%d",&n); 33 int bnum=(int)sqrt(n+0.5); 34 for(int i=1;i<=bnum;i++) 35 { 36 L[i]=(i-1)*sqrt(n+0.5)+1; 37 R[i]=i*sqrt(n+0.5); 38 } 39 if(R[bnum]<n) bnum++,L[bnum]=R[bnum-1]+1,R[bnum]=n; 40 for(int i=1;i<=n;i++) 41 scanf("%d",&val[i]); 42 for(int i=1;i<=bnum;i++) 43 { 44 for(int j=R[i];j>=L[i];j--)//倒序!!! 45 { 46 pos[j]=i; 47 if(j+val[j]<=R[i]) { 48 to[j]=to[j+val[j]]; 49 f[j]=f[j+val[j]]+1; 50 } 51 else f[j]=1,to[j]=j+val[j]; 52 } 53 }//对于块把,每个要维护的信息进行块内的预处理 54 int m; 55 scanf("%d",&m); 56 for(int i=1;i<=m;i++) 57 { 58 int opt; 59 scanf("%d",&opt); 60 if(opt==1) 61 { 62 int st; 63 scanf("%d",&st); 64 printf("%d\n",query(++st)); 65 } 66 if(opt==2) 67 { 68 int j,k; 69 scanf("%d%d",&j,&k); 70 val[++j]=k; 71 change(pos[j]); 72 } 73 } 74 } 75 /* 76 4 77 1 2 1 1 78 3 79 1 1 80 2 1 1 81 1 1 82 */
黑题:蒲公英(区间众数)
https://www.cnblogs.com/acfunction/p/10051345.html
整体思路:
分块 考虑维护什么:
对于整块 希望直接得到众数及其出现的次数
对于零散处 暴力求出每个数出现次数 但还要知道它们在整块中的出现次数 来更新答案
维护两个要预处理的数组:ans表示块与块之间的众数是什么 sum表示某个数在某一块之前出现的次数
O(n*sqrt(n))预处理
细节:要离散化 用桶来记录比较出现次数 要记得还原
ai≤1e9 ,先离散化。然后
算法一:暴力 n^2,预计得分 20 ; 实际得分 20 (开了 O2 直接变成 85 啥操作)
算法二:n≤40000 ,看来需要搬出分块大法。
离散化部分:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
for(int i=1;i<=n;++i) cin>>a[i].w,a[i].num=i; sort(a+1,a+n+1,cmp1); for(int i=1;i<=n;++i){ if(a[i].w!=a[i-1].w){ s[++tot]=a[i].w; a[i].x=tot; } else a[i].x=a[i-1].x; } sort(a+1,a+n+1,cmp2);
然后对于求众数:
我们考虑开个桶来存:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int solve(int l,int r) { int p=pos[l],q=pos[r]; if(p==q){//如果在同一个块中 就暴力for int now=0; for(int i=l;i<=r;i++){ t[num[i]]++; if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i]; } for(int i=l;i<=r;i++) t[num[i]]=0;//每次记得还原 return b[now]; } int now=ans[p+1][q-1]; t[now]+=sum[now][q-1]-sum[now][p];//初始化整块之间的众数及其出现次数 for(int i=l;i<=end[p];i++){//同样用桶去比较 if(!t[num[i]]) t[num[i]]+=sum[num[i]][q-1]-sum[num[i]][p]; t[num[i]]++; if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i]; } for(int i=sta[q];i<=r;i++){ if(!t[num[i]]) t[num[i]]+=sum[num[i]][q-1]-sum[num[i]][p]; t[num[i]]++; if(t[num[i]]>t[now]||(t[num[i]]==t[now]&&now>num[i])) now=num[i]; }//最后记得所有的清空 t[ans[p+1][q-1]]=0; for(int i=l;i<=end[p];i++) t[num[i]]=0; for(int i=sta[q];i<=r;i++) t[num[i]]=0; return b[now]; }
历史研究(回滚莫队) dalao:https://www.cnblogs.com/Parsnip/p/10969989.html#1745587478
考虑如果用正常莫队的话我们是无法删除的,因为一旦删除了最大元素就无法找到次大元素
对于回滚莫队:
这名字真的取的有意思,一开始还不觉得,学了之后发现他真的在回滚.我们回滚莫队排序是按之前的莫队一样将询问 左块右位置 的来排序.
回滚问题特别之处在哪里?
他在滚.
特别之处1
我们对于左右端点都在同一块里的,直接暴力for 1遍统计答案,复杂度根号n.
特别之处2
我们是枚举每一块来统计答案的.此时我们只处理左端点在此块,右端点在右块的询问,左右同块的见1.我们把ql指针指向下一块的开头,qr指针指向这一块的末尾.对于此本块询问,我们先将qr指针向右移更新信息直到此询问的右端点.(当我们处理下一个询问的时候,qr只需要继续右移就可以了,因为询问左端点都同块的时候,我们是按右端点位置从小到大排序的,所以单调递增).
这里好像没有回滚,并且由于qr单调递增,区间扩大,并没有删除操作.
但是我还没讲ql.
本块包含询问的左端点一定在ql左边(见ql定义).我们就将ql向左移更新答案(此时区间仍然在扩大,只有更新操作没有删除操作),到了询问的左端点,统计答案.
然后就开始滚了,并且是回滚.ql向左到达询问左端点后,向右往初始位置(见ql定义)移,删除之前一路滚过来所更新的信息.然后ql回滚回来,qr仍不动,处理下个询问.
所以就是 qr向右移动更新信息到询问右端点->ql向左滚 边滚边更新 至询问左端点,然后处理此区间答案,ql回滚删除之前一路滚来更新的信息,回到初位置->处理(下个询问->本块询问处理完->枚举下个块.
回滚得十分形象.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 #include<cstring> 5 #define LL long long 6 using namespace std; 7 const int MAXN = 1e5 + 10, INF = 1e9 + 7; 8 inline int read() { 9 char c = getchar(); int x = 0, f = 1; 10 while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} 11 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 12 return x * f; 13 } 14 int N, Q; 15 LL out[MAXN], ans; 16 int be[MAXN], date[MAXN], cnt[MAXN], a[MAXN], tot, base, num; 17 struct Query { 18 int l, r, id; 19 bool operator < (const Query &rhs) const { 20 if(be[l]==be[rhs.l]) return r<rhs.r; 21 else return be[l]<be[rhs.l]; 22 } 23 }q[MAXN]; 24 LL solve(int l, int r) { 25 //每个数*出现次数的最大值 26 //对块内,直接for 27 static int tim[MAXN]; 28 LL ans = 0; 29 for(int i = l; i <= r; i++) 30 tim[a[i]] = 0; 31 for(int i = l; i <= r; i++) 32 tim[a[i]]++, ans = max(ans, 1ll * tim[a[i]] * date[a[i]]); 33 return ans; 34 } 35 void Add(int x) {//考虑加元素带来的影响 36 cnt[a[x]]++; 37 ans = max(ans, 1ll * cnt[a[x]] * date[a[x]]); 38 } 39 void Del(int x) { 40 cnt[a[x]]--; 41 } 42 int Get(int i, int id) { 43 int R = min(N, id * base), ql = R + 1, qr = ql - 1; 44 ans = 0; 45 memset(cnt, 0, sizeof(cnt)); 46 for(; be[q[i].l] == id; i++) { 47 if(be[q[i].l] == be[q[i].r]) 48 { 49 out[q[i].id] = solve(q[i].l, q[i].r); 50 continue; 51 } 52 while(qr < q[i].r) Add(++qr);//右端点从小到大排的序,不断往右走 53 LL cur = ans; 54 while(ql > q[i].l) Add(--ql); 55 out[q[i].id] = ans; 56 57 while(ql < R + 1) Del(ql++);//每次询问完之后重新统计答案,回滚的时候删除以前更新的信息 58 ans = cur; 59 } 60 return i; 61 } 62 main() { 63 //freopen("4241.in", "r", stdin); 64 //freopen("4241.out", "w", stdout); 65 N = read(); Q = read(); base = sqrt(N); 66 for(int i = 1; i <= N; i++) { 67 a[i] = date[i] = read(); 68 be[i] = (i - 1) / base + 1;//每个所在的块的位置 69 num = max(num, be[i]); 70 } 71 72 sort(date + 1, date + N + 1); 73 int tot = unique(date + 1, date + N + 1) - date - 1;//去重 74 //tot kinds of different numbers 75 for(int i = 1; i <= N; i++) 76 a[i] = lower_bound(date + 1, date + N + 1, a[i]) - date; 77 //离散化 78 for(int i = 1; i <= Q; i++) 79 q[i].l = read(), q[i].r = read(), q[i].id = i; 80 sort(q + 1, q + Q + 1); 81 82 for(int i = 1, id = 1; id <= num; id++)//对于每个块开始回滚 83 i = Get(i, id); 84 85 for(int i = 1; i <= Q; i++) 86 printf("%lld\n", out[i]); 87 88 return 0; 89 } 90 /* 91 3 2 92 2 3 93 2 94 3 95 2 96 2 3 97 2 98 3 99 */
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步