题目大意:
题目链接:https://www.luogu.org/problemnew/show/P2048
一个长度为
n
n
n的序列,求
m
m
m个长度在
[
l
,
r
]
[l,r]
[l,r]之间的子序列,使得这些子序列的元素之和最大。
思路:
显然暴力是很难搞的,考虑先用前缀和。
那么我们要求的就是
∑
i
=
1
m
m
a
x
(
s
u
m
[
k
]
−
s
u
m
[
x
−
1
]
)
(
x
+
l
−
1
≤
k
≤
x
+
r
−
1
)
\sum^{m}_{i=1}max(sum[k]-sum[x-1])(x+l-1\leq k\leq x+r-1)
i=1∑mmax(sum[k]−sum[x−1])(x+l−1≤k≤x+r−1)
考虑拆成
m
m
m次询问。
对于每一次询问,我们要求
m
a
x
(
s
u
m
[
k
]
−
s
u
m
[
x
−
1
]
)
max(sum[k]-sum[x-1])
max(sum[k]−sum[x−1])。显然对于固定的
x
x
x,
s
u
m
[
x
−
1
]
sum[x-1]
sum[x−1]也是固定的。所以我们只要求出
m
a
x
(
s
u
m
[
k
]
)
max(sum[k])
max(sum[k])即可。
而
k
k
k是有范围要求的。当左端点为
x
x
x时,右端点的区间是固定的。我们每次要在这个区间内求最大,然后再求次大,接下来求第三大。
这显然就是一个静态区间第
k
k
k大的问题。直接主席树搞定就可以了。
那么我们要把所有的点作为左端点,然后求出相应的右端点,并插入堆里。每次取最大值然后维护一下就可以了。
其实这一步就是序列合并了。里面会讲解的更详细一些。
这样初始化
O
(
n
log
n
)
O(n\log n)
O(nlogn),每次询问
O
(
log
n
)
O(\log n)
O(logn),共
m
m
m次询问,总时间复杂度就是
O
(
m
log
n
)
O(m\log n)
O(mlogn)。
要开
l
o
n
g
l
o
n
g
long\ long
long long。
代码:
#include <map>
#include <cmath>
#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define mp make_pair
using namespace std;
typedef long long ll;
priority_queue<pair<ll,int> > q;
const int N=500010,M=500010*25;
int n,m,L,R,T,tot,root[N],num[N];
ll sum[N],b[N],ans;
struct Tree
{
int ls,rs,cnt;
}tree[M];
int build(int l,int r)
{
int p=++tot;
if (l==r) return p;
int mid=(l+r)/2;
tree[p].ls=build(l,mid);
tree[p].rs=build(mid+1,r);
return p;
}
int insert(int now,int l,int r,int k)
{
int p=++tot;
tree[p]=tree[now];
tree[p].cnt++;
if (l==r) return p;
int mid=(l+r)/2;
if (k<=mid) tree[p].ls=insert(tree[p].ls,l,mid,k);
else tree[p].rs=insert(tree[p].rs,mid+1,r,k);
return p;
}
int ask(int x,int y,int l,int r,int k)
{
if (l==r) return l;
int mid=(l+r)/2,sum=tree[tree[y].ls].cnt-tree[tree[x].ls].cnt;
if (sum>=k)
return ask(tree[x].ls,tree[y].ls,l,mid,k);
else return ask(tree[x].rs,tree[y].rs,mid+1,r,k-sum);
}
//以上主席树板子
int main()
{
scanf("%d%d%d%d",&n,&m,&L,&R);
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
sum[i]=sum[i-1]+x;
b[i]=sum[i];
}
sort(b+1,b+1+n);
T=unique(b+1,b+1+n)-b-1;
root[0]=build(1,T);
for (int i=1;i<=n;i++)
root[i]=insert(root[i-1],1,T,lower_bound(b+1,b+1+T,sum[i])-b);
for (int i=1;i<=n;i++)
{
int l=i+L-1,r=min(i+R-1,n);
if (l>n) break;
ll x=b[ask(root[l-1],root[r],1,T,(r-l+1))];
q.push(mp(x-sum[i-1],i));
num[i]=1;
}
while (m--)
{
ans+=q.top().first;
int i=q.top().second;
q.pop();
num[i]++;
int l=i+L-1,r=min(i+R-1,n);
if (l<=n&&num[i]<=r-l+1)
{
ll x=b[ask(root[l-1],root[r],1,T,(r-l+1-num[i]+1))];
//静态区间第k大=静态区间第r-l+1-k+1小
q.push(mp(x-sum[i-1],i));
}
}
printf("%lld\n",ans);
return 0;
}