数列区间询问中的分块思想

分块算法主要用于给定序列的区间询问问题,能够以较小的时间代价暴力求解,时间复杂度一般在O(n*n^0.5)。关键在O(1) 维护好某一区间在增加或者减少一个边界元素所带来的影响。需要注意的就是在更新的区间的时候要先放大在缩小,否则可能出现当前区间左右边界互换的情况,这 个影响某一些题可能没有影响,但是极有可能出错。

时间复杂度:先考虑左边界的时间复杂度,由于分成了sqrt(n)块,而同一块中左标移动的范围最多是sqrt(n),那相邻块跳 转的情况呢?可以虚拟出每块中有至少一个询问进行思考,那么相邻块之间的移动次数最大为2*sqrt(n)。由于共有Q次询问,因此最终时间复杂度为 O(Q*sqrt(n))。再考虑右边界,对于同一块内的右边界来说,其值是单调递增的,因此时间复杂度为O(n),相邻块跳转为O(2*n),由于共有 sqrt(n)块,因此最终时间复杂度为O(n*sqrt(n))。

---以上参考自 http://www.cnblogs.com/Lyush/archive/2013/08/16/3263247.html

 

1. Codefoces 86D Powerful array

 

题意: 给定n个数的一个序列, 再给出t组查询 (n, t <= 200000)问区间[l, r]里每个数的出现次数的平方乘上这个数的和是多少。

分析:分块的模板题,将询问离线处理,令sum[x]表示x在区间内出现的次数。扫描到ai时,若是进入区间则ans+=(2*sum[ai]+1)*ai, sum[ai]++,若是出区间则sum[ai]--,ans-=(2*sum[ai]+1)*ai。暴力维护区间即可。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<queue>
 7 #include<vector>
 8 #include<set>
 9 #include<string>
10 typedef long long LL;
11 using namespace std;
12 #define pii pair<int,int>
13 const int inf = 0x3f3f3f3f;
14 const int N = 200010;
15 
16 struct Node{
17     int id;
18     int L, R, bel;
19     Node(int id=0, int L=0, int R=0, int bel=0) : id(id),L(L),R(R),bel(bel){}
20     bool operator < (const Node &cmp) const {
21         if(bel != cmp.bel) return bel < cmp.bel;
22         return R < cmp.R;
23     }
24 } node[N];
25 LL a[N];
26 LL ans[N];
27 int cnt[1000100];
28 int L, R;
29 LL ret;
30 
31 LL query(int l, int r)
32 {
33     for(int i=l; i<L; i++) {
34         ret += (cnt[a[i]]<<1|1)*a[i];
35         cnt[a[i]]++;
36     }
37     for(int i=R+1; i<=r; i++) {
38         ret += (cnt[a[i]]<<1|1)*a[i];
39         cnt[a[i]]++;
40     }
41     for(int i=L; i<l; i++) {
42         cnt[a[i]]--;
43         ret -= (cnt[a[i]]<<1|1)*a[i];
44     }
45     
46     for(int i=r+1; i<=R; i++) {
47         cnt[a[i]]--;
48         ret -= (cnt[a[i]]<<1|1)*a[i];
49     }
50     L = l, R = r;
51     return ret;
52 }
53 
54 int main()
55 {
56     int i,j,k,m,n, x, y;
57     while(scanf("%d %d",&n,&m) == 2)
58     {
59         int block = sqrt(n*1.0);
60         for(i=1; i<=n; i++) scanf("%I64d",a+i);
61         for(i=1; i<=m; i++) {
62             scanf("%d %d",&x,&y);
63             node[i] = Node(i, x, y, x/block);
64         }
65         sort(node+1, node+1+m);
66         memset(cnt, 0, sizeof(cnt));
67         L = R = ret = 0;
68         for(i=1; i<=m; i++) {
69             ans[node[i].id] = query(node[i].L, node[i].R);
70         }
71         for(i=1; i<=m; i++) printf("%I64d\n", ans[i]);
72     }
73     return 0;
74 }
View Code

 

2. HDU 4638 Group

 

题意:给定一个序列,序列由1-N个元素全排列而成,求任意区间连续的段数。例如序列2,3,5,6,9就是三段(2, 3) (5, 6)(9)。

分析:也是离线处理,分块排好序,O(1)的维护就是每新添加一个元素 t 查看 t-1 与 t+1 是否都在区间内,如是则段数-1,如果都不在,则段数+1。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<queue>
 7 #include<vector>
 8 #include<set>
 9 #include<string>
10 typedef long long LL;
11 using namespace std;
12 #define pii pair<int,int>
13 const int inf = 0x3f3f3f3f;
14 const int N = 100010;
15 
16 struct Node{
17     int id;
18     int L, R, bel;
19     Node(int id=0, int L=0, int R=0, int bel=0) : id(id),L(L),R(R),bel(bel){}
20     bool operator < (const Node &cmp) const {
21         if(bel != cmp.bel) return bel < cmp.bel;
22         return R < cmp.R;
23     }
24 } node[N];
25 int a[N];
26 int ans[N];
27 int vis[N];
28 int L, R;
29 int ret;
30 
31 int query(int l, int r, int k)
32 {
33     if(k == 1) {
34         ret = 0;
35         for(int i=l; i<=r; i++) {
36             if(vis[a[i]-1] && vis[a[i]+1])
37                 ret--;
38             else if(!vis[a[i]-1] && !vis[a[i]+1])
39                 ret++;
40             vis[a[i]] = 1;
41         }
42         L = l, R = r;
43         return ret;
44     }
45     for(int i=l; i<L; i++) {
46         if(vis[a[i]-1] && vis[a[i]+1])
47             ret--;
48         else if(!vis[a[i]-1] && !vis[a[i]+1])
49             ret++;
50         vis[a[i]] = 1;
51     }
52     for(int i=R+1; i<=r; i++) {
53         if(vis[a[i]-1] && vis[a[i]+1])
54             ret--;
55         else if(!vis[a[i]-1] && !vis[a[i]+1])
56             ret++;
57         vis[a[i]] = 1;
58     }
59     for(int i=L; i<l; i++) {
60         if(vis[a[i]-1] && vis[a[i]+1])
61             ret++;
62         else if(!vis[a[i]-1] && !vis[a[i]+1])
63             ret--;
64         vis[a[i]] = 0;
65     }
66     for(int i=r+1; i<=R; i++) {
67         if(vis[a[i]-1] && vis[a[i]+1])
68             ret++;
69         else if(!vis[a[i]-1] && !vis[a[i]+1])
70             ret--;
71         vis[a[i]] = 0;
72     }
73     L = l, R = r;
74     return ret;
75 }
76 
77 int main()
78 {
79     int i,j,k,m,n, x, y;
80     int t;
81     scanf("%d",&t);
82     while(t--)
83     {
84         scanf("%d%d",&n,&m);
85         int block = sqrt(n*1.0);
86         for(i=1; i<=n; i++) scanf("%d",a+i);
87         for(i=1; i<=m; i++) {
88             scanf("%d %d",&x,&y);
89             node[i] = Node(i, x, y, x/block);
90         }
91         sort(node+1, node+1+m);
92         memset(vis, 0, sizeof(vis));
93         for(i=1; i<=m; i++) {
94             ans[node[i].id] = query(node[i].L, node[i].R, i);
95         }
96         for(i=1; i<=m; i++) printf("%d\n", ans[i]);
97     }
98     return 0;
99 }
View Code

 

3. HDU 4676 Sum Of Gcd

 

题意:给定一个1-N的全排列序列,N<=20000,有Q组询问,Q<=20000,每组询问给出左右区间[l, r],问区间内的任意两个数(ai,aj, i<j)的gcd之和为多少?

分析:预处理出所有数的因子,考虑每个因子对答案的贡献。其实可以把这些因子分为两类,一类是gcd(ai,ai,i<j)的集合,一类是这些gcd的所有因子的集合。我们只希望得到的是gcd集合的元素和,但是又不能维护这两个集合,这时就要用到定理:n = ∑(phi(x), x|n),即一个数等于其所有因子的欧拉函数之和,ok,那我们只需维护区间内的所有数的因子的欧拉函数之和就相当于维护区间内的gcd之和了。区间维护依旧使用分块思想。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<queue>
 7 #include<vector>
 8 #include<set>
 9 #include<string>
10 typedef long long LL;
11 using namespace std;
12 #define pii pair<int,int>
13 const int inf = 0x3f3f3f3f;
14 const int N = 20010;
15 
16 struct Node{
17     int id;
18     int L, R, bel;
19     Node(int id=0, int L=0, int R=0, int bel=0) : id(id),L(L),R(R),bel(bel){}
20     bool operator < (const Node &cmp) const {
21         if(bel != cmp.bel) return bel < cmp.bel;
22         return R < cmp.R;
23     }
24 } node[N];
25 int a[N], ans[N], vis[N], cnt[N];
26 int L, R, ret;
27 vector<int> divisor[N];
28 int phi[N];
29 void init()
30 {
31     memset(vis, 0, sizeof(vis));
32     for(int i=1; i<N; i++)
33         for(int j=i; j<N; j+=i)
34             divisor[j].push_back(i);
35     memset(phi, 0, sizeof(phi));
36     phi[1] = 1;
37     for(int i = 2; i < N; i++) if(!phi[i])
38         for(int j = i; j < N; j+=i)
39         {
40             if(!phi[j]) phi[j] = j;
41             phi[j] = phi[j] / i * (i-1);
42         }
43 }
44 
45 void update(int x, int tag)
46 {
47     for(int i=0; i<divisor[x].size(); i++) {
48         if(tag == 1)
49             ret += phi[divisor[x][i]] * cnt[divisor[x][i]]++;
50         else
51             ret -= --cnt[divisor[x][i]] * phi[divisor[x][i]];
52     }
53 }
54 
55 int query(int l, int r, int k)
56 {
57     if(k == 1) {
58         ret = 0;
59         for(int i=l; i<=r; i++) update(a[i], 1);
60         L = l, R = r;
61         return ret;
62     }
63     for(int i=l; i<L; i++)    update(a[i], 1);
64     for(int i=R+1; i<=r; i++) update(a[i], 1);
65     for(int i=L; i<l; i++)    update(a[i], -1);
66     for(int i=r+1; i<=R; i++) update(a[i], -1);
67     L = l, R = r;
68     return ret;
69 }
70 
71 int main()
72 {
73     init();
74     int i, j, k, m, n, x, y;
75     int t, cas = 0;
76     scanf("%d",&t);
77     while(t--)
78     {
79         scanf("%d",&n);
80         int block = sqrt(n*1.0);
81         for(i=1; i<=n; i++) scanf("%d",a+i);
82         scanf("%d",&m);
83         for(i=1; i<=m; i++) {
84             scanf("%d %d",&x,&y);
85             node[i] = Node(i, x, y, x/block);
86         }
87         sort(node+1, node+1+m);
88         memset(cnt, 0, sizeof(cnt));
89         for(i=1; i<=m; i++) {
90             ans[node[i].id] = query(node[i].L, node[i].R, i);
91         }
92         printf("Case #%d:\n", ++cas);
93         for(i=1; i<=m; i++) printf("%d\n", ans[i]);
94     }
95     return 0;
96 }
View Code

 

转载于:https://www.cnblogs.com/WCB-ACM/p/4951372.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值