离线处理(线段树|树状数组)| 莫对算法 —— HDU 4638 Group

对应HDU题目:点击打开链接

Group

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1730    Accepted Submission(s): 906


Problem Description
There are n men ,every man has an ID(1..n).their ID is unique. Whose ID is i and i-1 are friends, Whose ID is i and i+1 are friends. These n men stand in line. Now we select an interval of men to make some group. K men in a group can create K*K value. The value of an interval is sum of these value of groups.  The people of same group's id must be continuous. Now we chose an interval of men and want to know there should be how many groups so the value of interval is max.
 

Input
First line is T indicate the case number.
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].
 

Output
For every query output a number indicate there should be how many group so that the sum of value is max.
 

Sample Input
  
  
1 5 2 3 1 2 5 4 1 5 2 4
 

Sample Output
  
  
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;
}











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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值