[NOI2010]超级钢琴

[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);
}

效率

1.png

\[\ \]

\[\ \]

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,但是效率差异显著

1.png

原文地址:https://www.cnblogs.com/chasedeath/p/11824950.html