HDU 4638 - Group(树状数组 / 线段树)

#题目:

http://acm.hdu.edu.cn/showproblem.php?pid=4638

#题意:

给出n(1<=n<=100000)个数代表人的id(1-n), 有m个询问区间(x, y), 求出区间中分成多少组使得组和最大, 同一组内的id必须连续.

#思路:

题目转化:要使得和最大,应该尽可能的使得连续id的分成一组, 所以就变成了一个区间中连续id的区间.

树状数组.

初始化时记录a[i] 在数组中的位置pos[a[i]] = i.

从左到右扫描,当前为a[i], 先假设a[i]不与前面的任何数同一组,则将区间[1, i]都加1. 若pos[a[i] + 1] < i, 说明a[i]可以加入前面a[i]+1的那个区间中, 则区间[1,pos[a[i]+1] ] 减1, 对于pos[a[i] - 1] 同样操作.

边处理边询问, 若v[j].r == i 则得到答案.

AC.

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;
const int maxn = 1e5+5;
struct node {
    int l, r, id;
    bool operator < (const node & A) const {
        if(A.r == r) return l < A.l;
        return r < A.r;
    }
}Q;
vector<node> v;

int n, q;
int a[maxn], c[maxn], pos[maxn], ans[maxn];

int lowbit(int x)
{
    return x&(-x);
}
void add(int i, int val)
{
    while(i <= n) {
        c[i] += val;
        i += lowbit(i);
    }
}
int sum(int x)
{
    int s = 0;
    while(x >= 1) {
        s += c[x];
        x -= lowbit(x);
    }
    return s;
}

void init()
{
    //memset(a, 0, sizeof(a));
    memset(c, 0, sizeof(c));
    //memset(pos, 0, sizeof(pos));
    //memset(ans, 0, sizeof(ans));
    v.clear();
}

int main()
{
//freopen("in", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--) {

        scanf("%d %d", &n, &q);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            pos[a[i]] = i;
        }
        
        v.clear();
        for(int i = 1; i <= q; ++i) {
            scanf("%d %d", &Q.l, &Q.r);
            Q.id = i;
            v.push_back(Q);
        }
        
        sort(v.begin(), v.end());
        memset(c, 0, sizeof(c));
        
        int j = 0;
        for(int i = 1; i <= n; ++i) {
            add(i, 1);

            if(a[i] + 1 <= n && pos[a[i]+1] < i) {
                add(pos[a[i]+1], -1);
            }

            if(a[i]-1 >= 1 && pos[a[i]-1] < i) {
                add(pos[a[i]-1], -1);
            }

            while(v[j].r == i) {
                ans[v[j].id] = sum(v[j].r) - sum(v[j].l-1);
                j++;
            }
        }

        for(int i = 1; i <= q; ++i) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

线段树. 单点更新, 成段求和.

AC.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int sumv[maxn<<2];
int p[maxn], ans[maxn];

void update(int pos, int l, int r, int k, int v)
{
    if(l == r) {
        sumv[k] += v;
    }
    else {
        int mid = (l+r)/2;
        if(pos <= mid) update(pos, l, mid, k*2, v);
        if(pos > mid) update(pos, mid+1, r, k*2+1, v);
        sumv[k] = sumv[k*2] + sumv[k*2+1];
    }
}
int query(int k, int l, int r, int x, int y)
{
    if(l >= x && r <= y) return sumv[k];
    else  {
        int ans = 0;
        int mid = (l + r)/2;
        if(x <= mid) ans += query(k*2, l, mid, x, y);
        if(y > mid) ans += query(k*2+1, mid+1, r, x, y);
        return ans;
    }
}

struct node {
    int l, r, id;
    bool operator < (const node &A) const {
        if(r == A.r) return l < A.l;
        return r < A.r;
    }
}Q;
vector<node> v;

int main()
{
//freopen("in", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--) {

        int n, q;
        scanf("%d %d", &n, &q);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            p[a[i]] = i;
        }
        v.clear();
        for(int i = 0; i < q; ++i) {
            scanf("%d %d", &Q.l, &Q.r);
            Q.id = i;
            v.push_back(Q);
        }

        sort(v.begin(), v.end());
        memset(sumv, 0, sizeof(sumv));

        int j = 0;
        for(int i = 1; i <= n; ++i) {
            update(i, 1, n, 1, 1);
            if(a[i]+1<=n && p[a[i]+1]<i) {
                update(p[a[i]+1], 1, n, 1, -1);
            }
            if(a[i]-1>=1 && p[a[i]-1]<i) {
                update(p[a[i]-1], 1, n, 1, -1);
            }
            while(v[j].r == i) {
                ans[v[j].id] = query(1, 1, n, v[j].l, v[j].r);
                j++;
            }
        }
        for(int i = 0; i < q; ++i) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值