文章目录
- 题目描述
- 解析
- 代码
传送门
题目描述
解析
首先,如果只有一个和弦,那么问题显然简单了
用前缀和结合ST表随便做做即可
然而
这次要求前k大的
怎么办呢?
参照之前有一道序列合并的做法
我们想到,可以先建一个优先队列,把以每个元素为开头的最大和弦求出来
然后每次弹出队首,再用队首的开头把加进来一些新的
问题是,我受那道题影响,觉得一定要加进来次大的
次大弹出就加第三大的,子子孙孙无穷匮也…
于是我就卡到这里了
----------我的思路和题解的分割线-------------
但是不必拘泥与原来的那个区间
可以把它拆开来
用三元组(st,l,r)表示st开头,终点在[l,r]的最大值和最大值位置
那么取出一个最大值位置在pl的三元组后,可以再弹进去两个:
(st,l,pl-1)(st,pl+1,r)
当然,要注意一些pl在边缘的特判
这样就迎刃而解啦
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+100;
int n,m,k;
int l,r;
int a[N],sum[N];
struct node{int st,l,r,pl,v;bool operator < (const node y)const{return v<y.v;}
};
struct node2{int v,pl;
};
node2 mx[N][25];
node2 merge(node2 x,node2 y){if(x.v>y.v) return x;else return y;
}
int mi[N],lg[N];
void solve(){mi[0]=1;for(int i=1;i<=20;i++) mi[i]=mi[i-1]<<1;int k=0;for(int i=1;i<=n;i++){if(i>=mi[k]) k++;lg[i]=k-1;}for(int i=1;i<=n;i++) mx[i][0]=(node2){sum[i],i};for(int k=1;k<=lg[n];k++){for(int i=1;i+mi[k]-1<=n;i++){mx[i][k]=merge(mx[i][k-1],mx[i+mi[k-1]][k-1]);}}return;
}
node2 ask(int x,int y){int k=lg[y-x+1];return merge(mx[x][k],mx[y-mi[k]+1][k]);
}
priority_queue<node>q;
int main(){scanf("%d%d%d%d",&n,&k,&l,&r);for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];solve();for(int i=1;i<=n;i++){if(i+l-1>n) break;int L=i+l-1,R=min(n,i+r-1);node2 x=ask(L,R);q.push((node){i,L,R,x.pl,x.v-sum[i-1]});}ll tot=0;for(int i=1;i<=k;i++){node o=q.top();q.pop();tot+=o.v;int st=o.st;if(o.pl!=o.l){node2 x=ask(o.l,o.pl-1);q.push((node){st,o.l,o.pl-1,x.pl,x.v-sum[st-1]});}if(o.pl!=o.r){node2 x=ask(o.pl+1,o.r);q.push((node){st,o.pl+1,o.r,x.pl,x.v-sum[st-1]});}}printf("%lld",tot);return 0;
}
/*
6
2002 4920
2003 5901
2004 2832
2005 3890
2007 5609
2008 3024
5
2002 2005
2003 2005
2002 2007
2003 2007
2005 2008
*/