【51Nod】1672 - 区间交(线段树 & 贪心)

来源:互联网 发布:如何提高淘宝排名 编辑:程序博客网 时间:2024/06/05 15:15

点击打开题目

1672 区间交
基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题
 收藏
 取消关注
小A有一个含有n个非负整数的数列与m个区间,每个区间可以表示为li,ri。
它想选择其中k个区间, 使得这些区间的交的那些位置所对应的数的和最大。(具体可以参照样例)

在样例中,5个位置对应的值分别为1,2,3,4,6,那么选择[2,5]与[4,5]两个区间的区间交为[4,5],它的值的和为10。
Input
第一行三个数n,k,m(1<=n<=100000,1<=k<=m<=100000)。接下来一行n个数ai,表示小A的数列(0<=ai<=10^9)。接下来m行,每行两个数li,ri,表示每个区间(1<=li<=ri<=n)。
Output
一行表示答案
Input示例
5 2 31 2 3 4 64 52 51 4
Output示例
10



想明白以后确实是不难的,就是前期想着不好想。为了证明自己真的是理解了,把数据按左端点排序自己拍了一遍。


解释一下这个题:

我们首先把左端点从小到大排序(掌握以后,这个排序不论是排右端点、从大到小都可以做的),然后我们依次枚举左端点,我们把当前枚举的左端点 l 当做区间交的左端点,那么很明显,其他的区间的左端点肯定是要小于 l ,那么也就是说,前 k - 1 个端点不用枚举。

接下来就是数据的处理了,我们枚举了左端点,然后找右端点的时候要尽量靠右,这个右端点该怎么找呢?我们用一个线段树记录右端点,当我们枚举第 x 个区间的时候,后面的区间的右端点都是干扰的,我们就先不让它加入线段树,当然,前面的端点都要在线段树里(当时是这一点没想到),然后在这些端点中找最靠右的右端点,然后算前缀和更新一下ans。

然后我们要处理 x+1 区间了,我们把 x+1 的右端点加入线段数,重复前面的操作即可。


代码如下:(写了详细的注释)

#include <stdio.h>#include <cstring>#include <algorithm>using namespace std;#define CLR(a,b) memset(a,b,sizeof(a))#define INF 0x3f3f3f3f#define LL long long#define MAX 100000#define L o<<1#define R o<<1|1LL sum[MAX+5];struct node{int st,endd;}a[MAX+5];struct Tree{int l,r;int cover;}tree[MAX<<2];bool cmp(node a,node b){return a.st < b.st;}void PushUp(int o){tree[o].cover = tree[L].cover + tree[R].cover;}void Build(int o,int l,int r){tree[o].l = l;tree[o].r = r;if (l == r){tree[o].cover = 0;//覆盖点全部初始化为0 return;}int mid = (l + r) >> 1;Build(L , l , mid);Build(R , mid + 1 , r);PushUp(o);}void UpDate(int o,int x)//x位置cover值加一 {if (tree[o].l == tree[o].r){tree[o].cover++;return;}int mid = (tree[o].l + tree[o].r) >> 1;if (mid >= x)UpDate(L , x);elseUpDate(R , x);PushUp(o);}int Query(int o,int x)//满足条件的最靠右的端点下标 {if (tree[o].l == tree[o].r)return tree[o].l;if (tree[R].cover >= x)//优先从右树中找满足条件的return Query(R , x);elsereturn Query(L , x-tree[R].cover);//右边不够剩下的点从左树找 }int main(){int n,k,m;int t;scanf ("%d %d %d",&n,&k,&m);sum[0] = 0;for (int i = 1 ; i <= n ; i++){scanf ("%d",&t);sum[i] = sum[i-1] + t;}for (int i = 1 ; i <= m ; i++)scanf ("%d %d",&a[i].st,&a[i].endd);sort (a+1 , a+1+m , cmp);//对左端点排序 Build(1,1,n);for (int i = 1 ; i <= k ; i++)//前k-1个数的左端点不用枚举,直接更新其右端点即可 UpDate(1,a[i].endd);LL ans = 0;a[m+1].endd = 1;//随便赋值一个,防止最后一次循环出问题 for (int i = k ; i <= m ; i++)//从第k个点开始{int pos = Query(1,k);if (pos >= a[i].st)ans = max (ans , sum[pos] - sum[a[i].st-1]);UpDate(1,a[i+1].endd);//将下一点的右端点加入,即其cover值加一 }printf ("%lld\n",ans);return 0;}


0 0
原创粉丝点击