小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 3 1 2 3 4 6 4 5 2 5 1 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|1
LL 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);
else
UpDate(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);
else
return 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;
}