P2048 [NOI2010] 超级钢琴
题意
给出一个长度为 的数组 ,求区间和前 大的长度在 之间的区间的区间和之和。
分析
显然选出的这前 大的区间的区间和肯定是单调不升的,我们可以二分最小的区间和 ,看区间和不小于 的区间是否超过 个。
要求区间和,显然可以直接用前缀和, 函数就是求 且 的 的个数是否大于等于 。
移项一下可以得到 ,对于每个 ,我们求出有多少个 满足要求即可。
如果直接从 枚举 的话,很难用什么数据结构在同时满足两个条件的情况下计数,但是我们可以改变一下枚举顺序,按 从大到小枚举,这样的话到下一个 时满足第一个条件的集合需要新加入的 也是从大到小的,加入时把第 个位置标记为 ,统计 的 的个数就是 的答案。
为了改变枚举顺序,我们可以把 和 分别从大到小排序,用双指针 树状数组维护即可。
二分出最大的 后,用同样的办法统计符合条件的 ,减去 就可以求到和,注意如果 的个数大于 ,要把多余的 减去。
令值域为 ,则时间复杂度为 ,由于这题的题号 2048 很二进制所以树状数组做法可以过。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=5e5+10;
int n,L,R,ff;
ll a[N],s[N],l,r,c1[N],c2[N],ans2,k,cnt,w,z;
struct asdf{
ll v;
int id;
}d1[N],d2[N];
bool cmp(asdf x,asdf y){
return x.v>y.v;
}
void add1(int x,ll v){
for(;x<=n;x+=x&-x)
c1[x]+=v;
}
ll ask1(int x){
ll ans=0;
if(x>n)
x=n;
for(;x;x-=x&-x)
ans+=c1[x];
return ans;
}
void add2(int x,ll v){
for(;x<=n;x+=x&-x)
c2[x]+=v;
}
ll ask2(int x){
ll ans=0;
if(x>n)
x=n;
for(;x;x-=x&-x)
ans+=c2[x];
return ans;
}
ll check(ll x,bool f){
ll ans=0;
for(int i=1;i<=n;i++)
c1[i]=0;
for(int i=1,j=1;i<=n;i++){
while(j<=n&&d1[j].v>=d2[i].v+x){
add1(d1[j].id,1);
if(f)
add2(d1[j].id,d1[j].v);
j++;
}
w=ask1(d2[i].id+R)-ask1(d2[i].id+L-1);
ans+=w;
if(f)
ans2+=ask2(d2[i].id+R)-ask2(d2[i].id+L-1)-d2[i].v*w;
}
return ans;
}
int main(){
n=read();k=read();L=read();R=read();
for(int i=1;i<=n;i++){
a[i]=read();
s[i]=s[i-1]+a[i];
d1[i].v=s[i];
d2[i].v=s[i-1];
d1[i].id=i;d2[i].id=i-1;
if(a[i]<0)
l+=a[i];
else
r+=a[i];
}
sort(d1+1,d1+n+1,cmp);
sort(d2+1,d2+n+1,cmp);
l*=k;r*=k;
while(l<r){
ll mid=(l+r+1)>>1;
if(check(mid,0)>=k)
l=mid;
else
r=mid-1;
}
ll p=check(l,1);
ans2-=(p-k)*l;
write(ans2);
return 0;
}

浙公网安备 33010602011771号