HDU 5700 区间交 (线段树)

该博客介绍了一个利用线段树解决寻找k个区间相交时,最大区间和的问题。通过按左端点排序,固定左端点并枚举右端点,更新线段树来维护已枚举过的右端点数量。最后查询是否存在第k个右端点,以找到最大区间和。

题目链接

题意:n个非负整数,给出m个区间,求k个区间相交的区间和的最大值。

思路:按左(右)端点从小到大排序,本文排序左端点。由于我们要求k个区间相交的那个区间的和,所以我们可以让这个相交区间某个端点是固定不变的,比如左端点,然后我们只要求出一个右端点,这个右端点一定要在当前固定左端点的右边才能符合,而且要尽量离得远,当然首先他必须是这k个区间中的一个区间的右端点。(有点混乱,但可能画图比较好理解)简单来说就是固定左端点,选出右端点符合这些条件。

比如测试用例 5 2 3       

   1 2 3 4 6

  4 52 5 1 4

我们按左端点排好序——>1 4 2 5 4 5

由于我们要找k个区间的和,因此,前面k - 1个区间的直接先记录好,这个用例就是,先记录了1 4,然后枚举剩下的左端点:

   ① 左端点枚举到2,新的右端点5需要记录,此时只有两个区间相交,所有可选的只有相交区间是2-4 区间和为9

   ② 左端点枚举到4,新的右端点5记录, 此时三个区间相交,而由于新的左端点4进入而产生的是其中的两个,

所以我们只看由4产生的两个4-4,4-5,其中区间和大的是4-5 和为10

以上过程就解释了,我们为什么根据左端点排序,然后再枚举左端点。再来解释最重要的问题,用线段树来维护什么值?

答案就是,我们用线段树来记录,有多少个右端点为r的区间我们已经枚举过。

每次枚举一个,这个右端点对应的叶子结点及其所在的子树的值就+1。

然后我们查询是否存在第k个右端点,而且我们要尽量得去右子树查,因为这表明右端点尽可能在右边,相应的和也就越大。

最后我们把查出来的右端点,与当前枚举的这个左端点比较,检查是否在这个左端点的右边即可。

区间和可用前缀和计算。

大致过程如上,我觉得看代码更清楚点。

cpp代码:

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

using namespace std;
typedef long long LL;
const int MAXN = 100000;
LL sum[MAXN<<3];
LL pre[MAXN+5];
struct Interval {
    int l, r;
}itv[MAXN+5];
bool cmp(Interval a,Interval b){
    if(a.l != b.l)
        return a.l < b.l;
    else
        return a.r < a.l;
}
void update(int rt, int l, int r, int x){
    if(l == r) {
        sum[rt]++;
        return;
    }
    int m = (l+r)>>1;
    if(x > m) update(rt<<1|1, m+1, r, x);
    else update(rt<<1, l, m, x);
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
int query(int rt, int l, int r, int val) {
    if(l == r)return l;
    int m = (l+r)>>1;
    if(sum[rt<<1|1] >= val)//先往右边查(左端点固定的情况下,越往右的区间和越大),如果右边不够,则去左边找几个补回来,其实就是一层一层抽丝剥茧,最后找到符合要求的叶子结点
        return query(rt<<1|1, m+1, r, val);
    else
        return query(rt<<1, l, m, val - sum[rt<<1|1]);
}
int main()
{
    LL p;
    int n,k,m;
    while(~scanf("%d%d%d", &n,&k,&m)){
        memset(sum,0,sizeof(sum));
        pre[0] = 0;
        for(int i = 1; i <= n; i++){
            scanf("%I64d",&p);
            pre[i] = pre[i-1] + p;
        }
        for(int i = 1; i <= m; i++){
            scanf("%d%d",&itv[i].l,&itv[i].r);
        }
        sort(itv+1, itv+m+1, cmp);
        for(int i = 1; i <= k-1; i++)//先将k-1个放入树中
            update(1,1,n,itv[i].r);
        LL ans = 0;
        for(int i = k; i <= m; i++) {
            update(1,1,n,itv[i].r);//将当前枚举的右端点放入树种
            int r = query(1,1,n,k);//查询k个区间重合的右端点
            if(r >= itv[i].l)//与当前枚举的左端点比较,因为可能这个新加入的区间不与其他区间相交,或不足k个
                ans = max(ans,pre[r] - pre[itv[i].l-1]);//前缀和求区间和
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

这题。。。java卡了输入输出时间。。。我不想折腾了,就直接写了个cpp版交。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值