對應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].
題意:
求區間內的數能分成多少組,每組包含的數應儘可能多;分到同一組的要求是:同一組內的數是連續自然數。
如本例: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;
}