题意: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版交。
该博客介绍了一个利用线段树解决寻找k个区间相交时,最大区间和的问题。通过按左端点排序,固定左端点并枚举右端点,更新线段树来维护已枚举过的右端点数量。最后查询是否存在第k个右端点,以找到最大区间和。
&spm=1001.2101.3001.5002&articleId=79163800&d=1&t=3&u=7a016e7f15d848948bb0d73ab1063535)
546

被折叠的 条评论
为什么被折叠?



