51nod 1672 区间交【线段树】【贪心】

来源:互联网 发布:js中prompt 编辑:程序博客网 时间:2024/06/05 03:43

Description

小A有一个含有n个非负整数的数列与m个区间,每个区间可以表示为li,ri。

它想选择其中k个区间, 使得这些区间的交的那些位置所对应的数的和最大。(是指k个区间共同的交,即每个区间都包含这一段,具体可以参照样例)

在样例中,5个位置对应的值分别为1,2,3,4,6,那么选择[2,5]与[4,5]两个区间的区间交为[4,5],它的值的和为10。

题解

刚看到题目感觉很可以用扫描线做,后来问了Lynstery大佬,发现扫描线不可以,然后就get到了新操作——线段树上二分。只要枚举左端点,我们要的最优解一定是所有左端点在当前点左边的线段中右端点在它右边的右端点的第K大(线段树二分)。

代码

#include<cstdio>#include<cstring>#include<algorithm>#define maxn 100006#define LL long longusing namespace std;inline char nc(){    static char buf[100000],*i=buf,*j=buf;    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;}inline int _read(){    char ch=nc();int sum=0;    while(!(ch>='0'&&ch<='9'))ch=nc();    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();    return sum;}struct data{    int l,r,num;}tree[maxn*4];int n,K,m,tot,num[maxn],lnk[maxn],nxt[maxn],son[maxn];LL ans,sum[maxn];void add(int x,int y){    nxt[++tot]=lnk[x];son[tot]=y;lnk[x]=tot;}void build(int p,int l,int r){    tree[p].l=l;tree[p].r=r;    if(l>=r)return;    int mid=(l+r)>>1;    build(p<<1,l,mid);build(p<<1|1,mid+1,r);}void update(int p,int k,int t){    if(tree[p].l>k||tree[p].r<k)return;    if(tree[p].l==tree[p].r){        tree[p].num+=t;        return;    }    update(p<<1,k,t);update(p<<1|1,k,t);    tree[p].num=tree[p<<1].num+tree[p<<1|1].num;}int query(int p,int k){    if(k>tree[p].num)return 0;    if(tree[p].l==tree[p].r)return tree[p].l;    if(tree[p<<1|1].num>=k)return query(p<<1|1,k);    return query(p<<1,k-tree[p<<1|1].num);}int main(){    freopen("segment.in","r",stdin);    freopen("segment.out","w",stdout);    n=_read();K=_read();m=_read();build(1,1,n);    for(int i=1,x;i<=n;i++)x=_read(),sum[i]=sum[i-1]+x;    for(int i=1,x,y;i<=m;i++)x=_read(),y=_read(),add(x,y),num[y]++;    for(int i=1;i<=n;i++){        for(int j=lnk[i];j;j=nxt[j]) if(son[j]>=i)update(1,son[j],1);        ans=max(ans,sum[query(1,K)]-sum[i-1]);        update(1,i,-num[i]);    }    printf("%lld\n",ans);    return 0;}
原创粉丝点击