对应HDU题目:点击打开链接
Group
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1730 Accepted Submission(s): 906
For each case first line is n, m(1<=n ,m<=100000) indicate there are n men and m query.
Then a line have n number indicate the ID of men from left to right.
Next m line each line has two number L,R(1<=L<=R<=n),mean we want to know the answer of [L,R].
1 5 2 3 1 2 5 4 1 5 2 4
1 2
题意:
求区间内的数能分成多少组,每组包含的数应尽可能多;分到同一组的要求是:同一组内的数是连续自然数。
如本例:3 1 2 5 4 在[1, 5]区间就被分成1组,因为1 2 3 4 5是连续自然数;在[2, 4]区间就被分成2组,其中1、2是一组,5是一组。
思路1:
对询问按右端点升序排序,用线段树或树状数组离线处理,用pre[]数组记录x是否已经询问过,如被询问过,pre[x] 表示x的位置;当你在加入第i个数x时,把它当作单独的一组,如果pre[x-1]不为初始值(-1之类的),总的组数(指区间[1, i])就-1,pre[x+1]类似;具体过程:
以 3 1 2 5 4 为例
排序后的询问变成
2 4
1 5
线段树或树状数组维护a[]数组的值,从左往右把n个数加入:
a[] 1 2 3 4 5
加入3: 1 0 0 0 0 pre[3] = 1
加入1: 1 1 0 0 0 pre[1] = 2
加入2: 0 0 1 0 0 pre[2] = 3,此时pre[1] 和 pre[3]都被询问过,a[1], a[2] 各 -1
加入5: 0 0 1 1 0 pre[5] = 4,此时到了查询[2, 4]区间,直接求a[2]~a[4]的和
加入4: -1 0 1 0 1 pre[4] = 5,此时pre[3] 和 pre[5]都被询问过,a[1], a[4] 各 -1,此时到了查询[1, 5]区间,直接求a[1]~a[5]的和
思路2:
暴力求解!还是以 3 1 2 5 4 为例:我们不难依次求出区间[1, 1], [1, 2],...,[1, 5] 的答案,对于要求区间[2, 4]的答案,我们可以在之前的基础上把最后一个数去掉可得区间[1, 4]的答案, 之后把第一个数去掉可求区间[2, 4]答案。
总的来说就是,我们如果已知区间 [l, r] 的答案,可以在O(1)内求解[l, r+1], [l, r-1], [l+1, r], [l-1, r],那我们就可以暴力求解所有答案;遗憾的是,直接求解复杂度太高,妥妥TLE~ 但是如果把所有的查询区间按某些顺序进行求解却可以大大降低复杂度,非常神奇,由此引入莫队算法:
在有之前所讲前提下我们若已知[l, r]区间的答案,现在要求[l', r']区间的答案,那 l 和 r 共要移动 |l - l'| + |r - r'| 的距离,如果把 [l, r] 和 [l', r'] 当成点 (l, r) 和点 (l', r') ,那 |l - l'| + |r - r'| 就是这两点的曼哈顿距离;所以如果要用暴力方法把所有的区间求完,那这些点至少要连成一棵生成树(平面点曼哈顿最小生成树:点击打开链接),如果要降低复杂度,毫无疑问要以点与点之间的曼哈顿距离为边长建立最小生成树然后遍历这些点的时候顺便转化为区间进行求解
然而常规的构图建树编程复杂度有点大,对于这类处理无修改区间问题,对区间进行排序时可以分块,分成sqrt(n)块,比如n = 9,那就把总区间分成3块(不能开方的略有差别),每个位置有各自的块号,下标为i的块号为 (i - 1) / sqrt(n):
下标:1 2 3 4 5 6 7 8 9
块号:0 0 0 1 1 1 2 2 2
之后把所有需要查询的区间按这样的方式排序:如果第i个区间跟第i + 1个区间的左端点不属于同一块,就按各自左端点所属的块号升序排序;否则按右端点升序排序
排好序后就可以暴力求解了,还没有完全明白这样做可以成功的原因~待思考,感谢这篇博客:点击打开链接
思路1代码:
线段树:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define N 100005
#define MAX(x, y) ((x)>(y)?(x):(y))
using namespace std;
int a[N];
int pre[N];
int ans[N];
typedef struct{
int l, r, id;
}Input;
Input in[N];
bool cmp(Input a, Input b)
{
return a.r < b.r;
}
struct LineTree{
void Push_up(int rt)
{
T[rt].group = T[rt<<1].group + T[rt<<1|1].group;
}
void Build(int rt, int l, int r)
{
T[rt].l = l;
T[rt].r = r;
T[rt].group = 1;
if(l == r) return;
T[rt].mid = ((l + r)>>1);
Build(rt<<1, l, T[rt].mid);
Build(rt<<1|1, T[rt].mid + 1, r);
Push_up(rt);
}
void Update(int rt, int pos)
{
if(T[rt].l == pos && T[rt].r == pos){
T[rt].group--;
return;
}
if(pos <= T[rt].mid) Update(rt<<1, pos);
else Update(rt<<1|1, pos);
Push_up(rt);
}
int Query(int rt, int l, int r)
{
if(l == T[rt].l && r == T[rt].r) return T[rt].group;
if(r <= T[rt].mid) return Query(rt<<1, l, r);
else if(l > T[rt].mid) return Query(rt<<1|1, l, r);
else return Query(rt<<1, l, T[rt].mid) + Query(rt<<1|1, T[rt].mid + 1, r);
}
typedef struct{
int l, mid, r, group;
}Node;
Node T[N<<2];
};
LineTree tree;
int main()
{
//freopen("in.txt","r",stdin);
int T;
scanf("%d", &T);
while(T--){
int n, m, i, j, k;
scanf("%d%d", &n, &m);
tree.Build(1, 1, n);
for(i = 1; i <= n; i++)
scanf("%d", a + i);
for(i = 0; i < m; i++){
scanf("%d%d", &in[i].l, &in[i].r);
in[i].id = i;
}
sort(in, in + m, cmp);
memset(pre, 0, sizeof(pre));
for(i = 1, j = 0; i <= n && j < m; i++){
if(pre[a[i]-1])
tree.Update(1, pre[a[i]-1]);
if(pre[a[i]+1])
tree.Update(1, pre[a[i]+1]);
pre[a[i]] = i;
while(j < m && in[j].r == i){
ans[in[j].id] = tree.Query(1, in[j].l, i);
j++;
}
}
for(i = 0; i < m; i++)
printf("%d\n", ans[i]);
}
return 0;
}
树状数组:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define N 100005
#define MAX(x, y) ((x)>(y)?(x):(y))
using namespace std;
int a[N];
int c[N];
int pre[N];
int ans[N];
typedef struct{
int l, r, id;
}Input;
Input in[N];
bool cmp(Input a, Input b)
{
return a.r < b.r;
}
inline int lowbit(int x)
{
return x & (-x);
}
void Update(int pos, int val)
{
while(pos < N){
c[pos] += val;
pos += lowbit(pos);
}
}
int Sum(int pos)
{
int s = 0;
while(pos > 0){
s += c[pos];
pos -= lowbit(pos);
}
return s;
}
int main()
{
//freopen("in.txt","r",stdin);
int T;
scanf("%d", &T);
while(T--){
int n, m, i, j, k;
scanf("%d%d", &n, &m);
memset(c, 0, sizeof(c));
for(i = 1; i <= n; i++)
scanf("%d", a + i);
for(i = 0; i < m; i++){
scanf("%d%d", &in[i].l, &in[i].r);
in[i].id = i;
}
sort(in, in + m, cmp);
memset(pre, 0, sizeof(pre));
for(i = 1, j = 0; i <= n && j < m; i++){
Update(i, 1);
if(pre[a[i]-1])
Update(pre[a[i]-1], -1);
if(pre[a[i]+1])
Update(pre[a[i]+1], -1);
pre[a[i]] = i;
while(j < m && in[j].r == i){
ans[in[j].id] = Sum(i) - Sum(in[j].l - 1);
j++;
}
}
for(i = 0; i < m; i++)
printf("%d\n", ans[i]);
}
return 0;
}
莫队算法(加了点技巧的暴力求解):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define N 100010
using namespace std;
int a[N];
int c[N];
int vis[N];
int ans;
int block[N];
typedef struct{
int l, r, id;
}Point;
Point p[N];
bool cmp(Point p1, Point p2)
{
if(block[p1.l] != block[p2.l]) return block[p1.l] < block[p2.l];
return p1.r < p2.r;
}
void Update(int x, int flag)
{
vis[x] = flag;
if(flag) ans += 1 - vis[x-1] - vis[x+1];
else ans += vis[x-1] + vis[x+1] - 1;
}
int main()
{
//freopen("in.txt","r",stdin);
int T;
scanf("%d", &T);
while(T--){
ans = 0;
memset(vis, 0, sizeof(vis));
int n, m, sqn;
scanf("%d%d", &n, &m);
sqn = (int)sqrt(double(n));
int i, j;
for(i = 1; i <= n; i++){
scanf("%d", a + i);
block[i] = (i - 1) / sqn;
}
for(i = 1; i <= m; i++){
scanf("%d%d", &p[i].l, &p[i].r);
p[i].id = i;
}
sort(p + 1, p + m + 1, cmp);
int ll = 1, rr = 0;
for(i = 1; i <= m; i++){
int tmp = p[i].id;
if(rr < p[i].r)
for(j = rr + 1; j <= p[i].r; j++)
Update(a[j], 1);
if(rr > p[i].r)
for(j = rr; j > p[i].r; j--)
Update(a[j], 0);
if(ll < p[i].l)
for(j = ll; j < p[i].l; j++)
Update(a[j], 0);
if(ll > p[i].l)
for(j = ll - 1; j >= p[i].l; j--)
Update(a[j], 1);
c[tmp] = ans;
ll = p[i].l;
rr = p[i].r;
}
for(i = 1; i <= m; i++)
printf("%d\n", c[i]);
}
return 0;
}