Problem 792: 求和
Time Limit: 1000 ms Memory Limit: 262144 KB
Problem Description
给你一个数列A[1..n],长度为n。
令f(l,r,k)为A[l..r]里的第k大的元素。
特别的,当r-l+1
题解
看到这道题
我们不难想出O(n^2)的暴力
枚举每一个区间
求出他的第k大
怎么求第k大呢
暴力扫描?主席树?(都是sd)
用一个优先队列(小根堆)维护就行了
接着开始想优化
觉得可以二分找出比小根堆堆顶大的下标
发现不是单调
gg了
然后发现O(nk)是可以a的
然后可以想到用一个双向队列
每次从小到大删除(添加比较麻烦)
计算每个点的贡献(不是区间)
找到以x为最右边的区间(x是第k大)
右移到以x为最左边的区间
比较坑的地方在于pre【】数组
一开始想当然的以为r会沿着lc【r】回去
后来发现在n+1处有自环(自己走到自己)
这样返回时也要走自环
记录pre【i】=j表示
第i次右移是从j走来的
区别:
lc【i】:每个点对应一个点
pre【i】:每次移动对应一个点(每个点对应多个点)
暴力
#include<cstdio>
#include<iostream>
#include<queue>
#define N 200010
using namespace std;
typedef long long LL;
int n,a[N],m;
LL ans;
int main(){
freopen("data.txt","r",stdin);
freopen("2.txt","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
priority_queue<int,vector<int>,greater<int> >Q;
for(int j=0;j<m;j++)Q.push(a[j+i]);
ans+=Q.top();
for(int j=i+m;j<=n;j++){
if(a[j]>Q.top()){
Q.pop();
Q.push(a[j]);
}
ans+=Q.top();
}
}
cout<<ans<<'\n';
}
ac代码
#include<cstdio>
#include<iostream>
#define N 200010
using namespace std;
typedef long long LL;
int n,m,p,a[N],b[N],lc[N],rc[N],l,r,h,t,cnt,tot,mid,pre[N];
LL ans;
int main(){
freopen("data.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&p);
b[p]=i;
lc[i]=i-1;
rc[i]=i+1;
}
rc[n+1]=n+1;
for(int i=1;i<=n;i++){
// printf("%d\n",i);
l=r=mid=b[i];
cnt=m;
while(cnt--){
pre[m-cnt]=r;
r=rc[r];
}
cnt=m;
while(cnt--){
ans+=(LL)(l-lc[l])*(r-pre[cnt+1])*i;
// printf("%d %d ",l,r);
// cout<<ans<<'\n';
l=lc[l];
r=pre[cnt+1];
}
rc[lc[b[i]]]=rc[b[i]];
lc[rc[b[i]]]=lc[b[i]];
}
cout<<ans<<'\n';
}