sqrt数据结构

简单题:弹飞绵羊

分块:
将序列分块,每块sqrt(n)个。
在每个块中维护f[i],to[i]
f[i] 表示跳几次可以跳出所在块
to[i] 表示跳出所在块后到达的位置。
在查询时,我们O(sqrt(n))的时间进行“整块”的模拟,可以得到结果。
在修改i时,我们只需维护一下(l[belong[i]]--i)的序列就可以了。
总复杂度:O(n*sqrt(n))

 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
",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 */
View Code


黑题:蒲公英(区间众数)

https://www.cnblogs.com/acfunction/p/10051345.html

整体思路:

分块 考虑维护什么:
对于整块 希望直接得到众数及其出现的次数  
对于零散处 暴力求出每个数出现次数 但还要知道它们在整块中的出现次数 来更新答案
维护两个要预处理的数组:ans表示块与块之间的众数是什么 sum表示某个数在某一块之前出现的次数
O(n*sqrt(n))预处理
细节:要离散化 用桶来记录比较出现次数 要记得还原

ai1e9 ,先离散化。然后

算法一:暴力 n^2,预计得分 20 ; 实际得分 20 (开了 O2 直接变成 85 啥操作)

算法二:n≤40000 ,看来需要搬出分块大法。

离散化部分:

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);
View Code

 然后对于求众数:

我们考虑开个桶来存:

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];
}
View Code


历史研究(回滚莫队)  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回滚删除之前一路滚来更新的信息,回到初位置->处理(下个询问->本块询问处理完->枚举下个块.
回滚得十分形象.

 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
", 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 */
回滚莫队


原文地址:https://www.cnblogs.com/lkx422/p/11236575.html