BZOJ 2006 超级钢琴(堆+主席树)

很好的一道题。

题意:给出长度为n的数列,选择k个互不相同的区间,满足每个区间长度在[L,R]内,求所有选择的区间和的总和最大是多少。(n,k<=5e5).

首先将区间和转化为前缀和之差,那么我们想要区间和的总和最大,一个朴素的想法是把所有满足限制的区间和排序,取最大的k个。

考虑每个右端点i,其中有效的左端点是[i-R+1,i-L+1].如果我们对于每个右端点都找到“当前”最大的区间和,那么把它们扔进大根堆里维护一下即可。

由于sum[i]一定,那么只要左端点的sum值越小越好,于是我们每次从大根堆里弹出一个数,再从弹出的这个值的右端点出发,找一个次大的值。

这样从大根堆里面弹出k次就是最大的k个区间和了。

那么我们需要维护一个数据结构支持查询静态区间第k小,显然主席树可以满足这个要求。

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <vector>
# include <queue>
# include <stack>
# include <map>
# include <bitset>
# include <set>
# include <cmath>
# include <algorithm>
using namespace std;
# define lowbit(x) ((x)&(-x))
# define pi acos(-1.0)
# define eps 1e-8
# define MOD 1000000007
# define INF 1000000000
# define mem(a,b) memset(a,b,sizeof(a))
# define FOR(i,a,n) for(int i=a; i<=n; ++i)
# define FO(i,a,n) for(int i=a; i<n; ++i)
# define bug puts("H");
# define lch p<<1,l,mid
# define rch p<<1|1,mid+1,r
# define mp make_pair
# define pb push_back
typedef pair<int,int> PII;
typedef vector<int> VI;
# pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long LL;
int Scan() {
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}
const int N=500005;
//Code begin...

int a[N], sum[N], vis[N];
struct qnode{
    int id, val;
    qnode(int _id=0, int _val=0):id(_id),val(_val){}
    bool operator<(const qnode &r)const{return val<r.val;}
};
priority_queue<qnode>que;
VI v;
int root[N], s[N*80], ls[N*80], rs[N*80], sz;

void insert(int l, int r, int x, int &y, int val){
    y=++sz; s[y]=s[x]+1;
    if (l==r) return ;
    ls[y]=ls[x]; rs[y]=rs[x];
    int mid=(l+r)>>1;
    if (val<=mid) insert(l,mid,ls[x],ls[y],val);
    else insert(mid+1,r,rs[x],rs[y],val);
}
int query(int l, int r, int x, int y, int L)
{
    if (l==r) return l;
    int mid=(l+r)>>1, val=s[ls[y]]-s[ls[x]];
    if (val>=L) return query(l,mid,ls[x],ls[y],L);
    return query(mid+1,r,rs[x],rs[y],L-val);
}
int main ()
{
    int n, k, L, R;
    qnode tmp;
    scanf("%d%d%d%d",&n,&k,&L,&R);
    FOR(i,1,n) scanf("%d",a+i), sum[i]=sum[i-1]+a[i], v.pb(sum[i]);
    v.pb(0);
    sort(v.begin(),v.end());
    int siz=unique(v.begin(),v.end())-v.begin();
    FOR(i,1,n+1) {
        int x=lower_bound(v.begin(),v.begin()+siz,sum[i-1])-v.begin()+1;
        insert(1,siz,root[i-1],root[i],x);
    }
    FOR(i,1,n) {
        int l=max(i-R,0), r=i-L;
        if (r<l) continue;
        int x=query(1,siz,root[l],root[r+1],1); que.push(qnode(i,sum[i]-v[x-1]));
    }
    int num=0;
    LL ans=0;
    while (num<k) {
        tmp=que.top(); que.pop(); ans+=tmp.val; ++vis[tmp.id]; ++num;
        int l=max(tmp.id-R,0), r=tmp.id-L;
        if (r-l<vis[tmp.id]) continue;
        int x=query(1,siz,root[l],root[r+1],vis[tmp.id]+1); que.push(qnode(tmp.id,sum[tmp.id]-v[x-1]));
    }
    printf("%lld\n",ans);
    return 0;
}

时间: 2024-08-03 10:14:46

BZOJ 2006 超级钢琴(堆+主席树)的相关文章

BZOJ 2006 NOI 2010 超级钢琴 堆+主席树

题目大意:给出一些音符,将它们组成和旋.和旋只能由[l,r]个音符组成.优美程度为所有音符的和.求k个和旋的又优美程度的最大和. 思路:先处理出来前缀和,以便O(1)去除一段的和.然后考虑对于一个音符来说,向左边扩展的音符是一段长度为r - l + 1的区间,取出的最大和是sum[i] - sum[p],sum[i]是一定的,要想让整段和最大,需要让sum[p]最小.之后就是区间k小值和堆得维护了,可以用时代的眼泪划分树,也可以用主席树. CODE: #include <vector> #in

Bzoj 2006: [NOI2010]超级钢琴 堆,ST表

2006: [NOI2010]超级钢琴 Time Limit: 20 Sec  Memory Limit: 552 MBSubmit: 2222  Solved: 1082[Submit][Status][Discuss] Description 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音符的美妙度为Ai,其中Ai可正可负. 一个“超级和弦”由若干个编号连续的音符组成,包含

bzoj 4504: K个串【大根堆+主席树】

像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么快速求区间和,用可持久化线段树维护(主席树?)每个点到他root的区间和,这样每次右端点右移就是上一个的线段树在(la[a[i]]+1,i)加上a[i],la是这个值a[i]上一次出现的位置 然后就可以在线处理询问了 有一点因为这个线段树建的是1~n,所以右端点不是n的时候取max会取到右端点向右还是初始值0的位置(有可能前面是负数),这样的解

[BZOJ 4571][Scoi2016]美味(主席树)

Description 一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 ai(1≤i≤n).有 m 位顾客,第 i 位顾客的期 望值为 bi,而他的偏好值为 xi .因此,第 i 位顾客认为第 j 道菜的美味度为 bi XOR (aj+xi),XOR 表示异或 运算.第 i 位顾客希望从这些菜中挑出他认为最美味的菜,即美味值最大的菜,但由于价格等因素,他只能从第 li 道到第 ri 道中选择.请你帮助他们找出最美味的菜. Solution 按位贪心,每次通过查询一段区间是

【BZOJ 3772】精神污染 主席树+欧拉序

这道题的内存-------真·精神污染---.. 这道题的思路很明了,我们就是要找每一个路径包含了多少其他路径那么就是找,有多少路径的左右端点都在这条路径上,对于每一条路径,我们随便选定一个端点作为第一关键字,另一个作为第二关键字,于是就有了两维限制,按照主席树的一般思路,我们把建树顺序作为一维,然后在里面维护另一维,那么我们在外面限制第一关键字,就是在树上建主席树,查询减LCA,在里面的话我们把每个点作为第一关键字对应的第二关键字,放入主席树,而主席树维护的是欧拉序区间,所以我们每次查询只用查

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

BZOJ 1878 HH的项链(主席树)

对于该题,离线的做法是树状数组或者线段树. 如果强制在线的话,可以用主席树做到O(mlogn). 考虑到这样一个性质,对于询问[l,r]出现的数字种数.其答案就是to[i]>r的数字数. 其中to[i]表示的是第i个数的下一个相同的数出现的下标,没有则=n+1. 很幸运这个性质是满足区间减法的,也就是说对于[1,r]和[1,l-1]的to[i]域,是可以相减得到[l,r]的to[i]域的. 于是我们可以用主席树来解决这个问题. 对于一组询问,实际上就是求[l-1,r]这颗线段树上的区间[r+1,

BZOJ 3524 POI 2014 Couriers 主席树

题目大意 给出一个序列,问一段区间内有没有出现过一半以上的数字. 思路 用主席树取区间出来,在权值线段树上找. CODE #define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 500010 #define MAXR 10000010 using namespace std

bzoj 1901: Zju2112 Dynamic Rankings -- 主席树,树状数组,哈希

1901: Zju2112 Dynamic Rankings Time Limit: 10 Sec  Memory Limit: 128 MB Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1 ],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改 变后的a继续回答上面的问题. Input 第一行有两个正整数n(1≤