[NOI2010]超级钢琴
提供两种写法
Part 1 - > \(n log ^2 n\)
\(k\)大问题常用思想,二分答案
离散后用树状数组维护左边\(j \in [i-R,i-L]\)距离内的前缀和(\(Sum\))的值满足\(Sum_i-Sum_j \ge Ans\)的个数
这是一种非常套路的写法
最后还要统计总和,可以再增加一个差分来统计
就统计 > \(Ans\) 的和,剩下的个数答案都是 \(Ans\),累加即可
(下面这份代码经历了卡常,不具有可读性)
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) f|=(IO=='-');
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=5e5+10,INF=1e9;
int n,m,l,r;
int s[N],h[N],hc;
int sum[N];
ll sum2[N];
void Add(reg int p,reg int x) {
while(p<=hc) sum[p]+=x,p+=p&-p;
}
void Add2(int p,int x,int y) {
while(p<=hc) sum[p]+=x,sum2[p]+=y,p+=p&-p;
}
int Que(reg int p) {
int res=0;
while(p) res+=sum[p],p-=p&-p;
return res;
}
ll Que2(int p) {
ll res=0;
while(p) res+=sum2[p],p-=p&-p;
return res;
}
ll Count(int lim) {
reg ll cnt=0;
rep(i,1,hc) sum[i]=0;
for(reg int i=l;i<=n;++i) {
((h[s[i-l]]+lim<=h[hc])&&(Add(s[i-l],1),0));
((h[s[i]]>=h[1]+lim)&&(cnt+=Que(
({
reg int l=1,r=hc,res=0;
while(l<=r) {
reg int mid=(l+r)>>1;
h[mid]+lim<=h[s[i]]?(l=mid+1,res=mid):r=mid-1;
}
res;
})
))); // 二分满足 s[i]-s[h[res]]>=lim
((i>=r && h[s[i-r]]+lim<=h[hc])&&(Add(s[i-r],-1),0));
}
return cnt;
}
int main(){
n=rd(),m=rd(),l=rd(),r=rd();
rep(i,1,n) s[i]=s[i-1]+rd();
rep(i,0,n) h[++hc]=s[i];
sort(h+1,h+hc+1),hc=unique(h+1,h+hc+1)-h-1;
rep(i,0,n) s[i]=lower_bound(h+1,h+hc+1,s[i])-h;
int l=-1e6,r=5e8,res=-1;
while(l<=r) {
int mid=(l+r)>>1;
if(Count(mid)>=m) l=mid+1,res=mid;
else r=mid-1;
}
res++;
ll ans=0,cnt=0;
memset(sum,0,sizeof sum);
rep(i,::l,n) {
Add2(s[i-::l],1,h[s[i-::l]]);
int t=upper_bound(h+1,h+hc+1,h[s[i]]-res)-h-1;
int c=Que(t);
ans+=1ll*c*h[s[i]]-Que2(t);
cnt+=c;
if(i>=::r) Add2(s[i-::r],-1,-h[s[i-::r]]);
}
ans+=1ll*(m-cnt)*(res-1);
printf("%lld\n",ans);
}
效率
\[\
\]
\[\
\]
Part2 - > \(nlogn\)
考虑用堆+st表维护最大值
对于每个\(i\),取\(j \in [i-R,i-L]\)中\(Sum_j\)最小的位置加入堆,同时记录两个值\([l,r]=[i-R,i-L]\),每次弹出堆后,分别取\([l,j-1],[j+1,r]\)两段中的最优位置,重新加入堆
最小值的位置用\(st\)表维护即可\(O(1)\)查询
因为这题的\(k\)比较小,所以如此重复\(k\)次就能得到答案
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iostream>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) f|=(IO=='-');
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=5e5+10,INF=1e9;
int n,m,L,R;
int a[N],s[N][20],p[N][20];
int Log[N];
int Que(int l,int r) {
int d=Log[r-l+1];
return s[l][d]<s[r-(1<<d)+1][d]?p[l][d]:p[r-(1<<d)+1][d];
}
struct Node{
int p,l,r,t,x;
bool operator < (const Node __) const {
return x<__.x;
}
};
priority_queue <Node> Q;
int main(){
n=rd(),m=rd(),L=rd(),R=rd();
rep(i,1,n) a[i]=a[i-1]+rd();
rep(i,2,n) Log[i]=Log[i>>1]+1;
drep(i,n,0) {
s[i][0]=a[i],p[i][0]=i;
for(reg int j=1;(1<<j)+i<=n+1;++j) {
if(s[i][j-1]<s[i+(1<<(j-1))][j-1]) {
s[i][j]=s[i][j-1];
p[i][j]=p[i][j-1];
} else {
s[i][j]=s[i+(1<<(j-1))][j-1];
p[i][j]=p[i+(1<<(j-1))][j-1];
}
}
}
ll ans=0;
rep(i,L,n) {
int t=Que(max(0,i-R),i-L);
Q.push((Node){i,max(0,i-R),i-L,t,a[i]-a[t]});
}
rep(i,1,m) {
Node t=Q.top(); Q.pop();
ans+=t.x;
if(t.l<t.t) {
int p=Que(t.l,t.t-1);
Q.push((Node){t.p,t.l,t.t-1,p,a[t.p]-a[p]});
}
if(t.t<t.r) {
int p=Que(t.t+1,t.r);
Q.push((Node){t.p,t.t+1,t.r,p,a[t.p]-a[p]});
}
}
printf("%lld\n",ans);
}
代码写得比较low,但是效率差异显著