HDU 5722 Jewelry【线段树,矩形面积并】

题意:

给定序列,求满足区间内存在一种珍珠正好出现 x 次的区间个数。

分析:

枚举左端点,找右端点的可行区间。
对于每一个珍珠,从头开始枚举左端点,对于每一个左端点l,维护满足区间 [l,r] 内该种珍珠正好出现 x 次的右端点r,由于存在一个连续的区间内,区间内珍珠的数目是不变的,那么我们把这些得到的满足条件的区间并起来, 就得到了左端点对应的可行右端点的区间,求区间并我们可以用线段树,类似求扫描线长度。
假设离开当前左端点到达下一个端点 l1 ,此时区间内珍珠少了一个,那么左端点在 [l,l1) 之间的可行右端点范围是相同,我们直接累加到答案即可。到达 l1 时,区间内珍珠数目发生改变,此时我们要将 l 对应的右区间删除,右端点后移使得区间内重新满足有x个珍珠,区间并的长度即扫描线长度发生改变。
对于每种珍珠都进行相同的处理,最终我们得到每个左区间对应的可行右区间,从头扫一遍,将扫描线长度累加起来即为答案。我们会发现其实本质就是求矩形面积并。
参照hezhu大神的代码理解和实现的。

代码:

/*************************************************************************
    > File Name: 5722.cpp
    > Author: jiangyuzhu
    > Mail: 834138558@qq.com
    > Created Time: 2016/7/18 14:59:14
 ************************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int, int>p;
typedef pair<int, p>pp;
typedef long long ll;
#define sa(n) scanf("%d", &(n))
const int maxn = 1e5 + 5, maxm = 1e6 + 5, mod = 1e9 + 7, oo = 0x3f3f3f3f;
int a[maxn], na[maxn];
struct Tree{
    int l, r;
    int len;
    int cnt;
}tree[(maxn << 2) + 5];
vector<int>cnt[maxn];
vector<pp>v[maxn];
void build(int l, int r, int i)
{
    tree[i].l = l;
    tree[i].r = r;
    tree[i].len = 0;
    tree[i].cnt = 0;
    if(l == r) return;
    int mid = l + r >> 1;
    build(l, mid, i << 1);
    build(mid + 1, r, (i << 1)|1);
}
void push_up(int i)
{
    if(tree[i].cnt) {
        tree[i].len = tree[i].r + 1 - tree[i].l;
    }else{
        if(tree[i].l == tree[i].r) tree[i].len = 0;
        else {
            tree[i].len = tree[i << 1].len + tree[(i << 1) | 1].len;
        }
    }
}
void update(int l, int r, int i, int k)
{
    if(l <= tree[i].l && r >= tree[i].r){
        tree[i].cnt += k;
        push_up(i);
        return;
    }
    int mid = tree[i].l + tree[i].r >> 1;
    if(r <= mid) update(l, r, i << 1,k);
    else if(l > mid) update(l, r, (i << 1)|1,k);
    else{
        update(l, mid, i <<1, k);
        update(mid + 1, r, (i<<1)|1, k);
    }
    push_up(i);
}
int main (void)
{
    int T;sa(T);
    while(T--){
        int n, x;sa(n);sa(x);
        for(int i = 0; i < n; i++){
            sa(a[i]);
            na[i] = a[i];
        }
        sort(na, na + n);
        int nn = unique(na, na + n) - na;
        for(int i = 0; i < n; i++){
            a[i] = lower_bound(na, na + nn, a[i]) - na;
            cnt[a[i]].push_back(i);
        }
        int al, ar, dl, dr;
        for(int j = 0; j < n; j++){
            if(cnt[j].size() < x) continue;
            for(int i = 0; i + x <= cnt[j].size(); i++){
                al = i?cnt[j][i - 1] + 1:0;
                ar = cnt[j][i] + 1;
                dl = cnt[j][i + x - 1];
                dr = i + x == cnt[j].size()? n - 1:cnt[j][i + x] - 1;
                v[al].push_back(pp(1, p(dl, dr)));
                v[ar].push_back(pp(-1, p(dl, dr)));
            }
        }
        build(0, n - 1, 1);
        ll ans = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < v[i].size(); j++){
                update(v[i][j].second.first, v[i][j].second.second, 1,  v[i][j].first);
            }
            ans +=  tree[1].len;
        }
        printf("%I64d\n", ans);
        for(int i = 0; i < n + 5; i++){
            cnt[i].clear();
            v[i].clear();
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值