51Nod 1672 区间交

33 篇文章 0 订阅
15 篇文章 0 订阅

基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
 收藏
 关注
小A有一个含有n个非负整数的数列与m个区间,每个区间可以表示为li,ri。
它想选择其中k个区间, 使得这些区间的交的那些位置所对应的数的和最大。(是指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
Joe  (题目提供者)


分析:

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


解释一下这个题:

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

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

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


#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       

#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
LL sum[MAXN + 5];
struct Node { int st,ed; }a[MAXN + 5];
struct Seg_Ment_Tree{ int l,r,sum; }tre[MAXN << 2];
inline bool cmp(Node a,Node b ) { return a.st < b.st; }

void Build(int u,int l,int r) {
    tre[u].l = l,tre[u].r = r;
    if(l == r){ tre[u].sum = 0; return ; }
    int Mid = l + r >> 1;
    Build(u<<1,l,Mid); Build(u<<1|1,Mid + 1,r);
    tre[u].sum = tre[u<<1].sum + tre[u<<1|1].sum;
}

void Modify(int u,int pos) {
    if(tre[u].l == tre[u].r) { tre[u].sum ++; return; }
    int Mid = tre[u].l + tre[u].r >> 1;
    if(pos <= Mid) Modify(u<<1,pos);
    else Modify(u<<1|1,pos);
    tre[u].sum = tre[u<<1|1].sum + tre[u<<1].sum;
}

int Query(int u,int x) {
    if(tre[u].l == tre[u].r) return tre[u].l;
    if(tre[u<<1|1].sum >= x) return Query(u<<1|1,x);
    else return Query(u<<1,x - tre[u<<1|1].sum);
}

int main(int argc,char *argv[]) {
    int n,k,m,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].ed);
    sort(a + 1, a + m + 1,cmp);
    Build(1,1,n);
    for(int i=1; i<=k; ++i) 
        Modify(1,a[i].ed);
    LL Ans = 0;
    a[m + 1].ed = 1;
    for(int i=k; i<=m; ++i) {
        LL pos = Query(1,k);
        if(pos >= a[i].st) Ans = max(Ans,sum[pos] - sum[a[i].st - 1]);
        Modify(1,a[i + 1].ed);
    }
    printf("%I64d\n",Ans);
    return 0;
}
      
      
     
     
    
    
   
   


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七情六欲·

学生党不容易~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值