【题解】【原创题目】せやな~

【题解】【原创题目】せやな~

出题人:辰星凌

验题人:\(\text{YudeS}\)

题目传送门:せやな~

【题目描述】

简化题意:求给定序列中的第 \(K\) 大区间和前 \(K\) 大区间和。

【分析】

【Solution #1】

纯暴力,把所有区间权值算出来排个序取前 \(K\) 个即可,注意开 \(\text{long long}\)

时间复杂度:\(O(n^2logn)\)

分数:\(10pt\)

【Code #1】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+5,M=1e6+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,A[N],S[N],Ans[M];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline bool cmp(Re a,Re b){return a>b;}
inline void out(){
    printf("%lld\n",Ans[K]);
    if(op){
        LL ans=0;
        for(Re i=1;i<=K;++i)(ans+=(Ans[i]+P)%P)%=P;
        printf("%lld\n",ans);
    }
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1;i<=n;++i)in(A[i]),S[i]=S[i-1]+A[i];
    for(Re i=1;i<=n;++i)
        for(Re j=1;j<=i;++j)
            Ans[++m]=S[i]-S[j-1];
    sort(Ans+1,Ans+m+1,cmp);
    out();
}

【Solution #2】

注意到一个很有用的性质: 对于每个 \(i \in [1,n]\),以 \(i\) 为右端点的区间中,对答案有贡献的一定不会超过 \(K\) 个。

\(O(nlogn)\) 预处理出一个数组 \(nex\),其中 \(nex[i]\) 表示比 \(i\) 大的数中最小的一个位置,如果相同则取靠左边的。

\(1\)\(n\) 枚举 \(i\),对于每个 \(i\) 都用 \(nex\) 暴跳一波,如果有位置 \(j\)\(i\) 左边,就把 \(S[i]-S[j]\) 加进队列 \(B_1\),然后再与答案队列 \(Ans\) 合并成新的答案队列,可知 \(B_1,Ans\) 均保持单调性,直接 \(O(K)\) 归并即可。

对于每个指针,最多可能会移 \(O(n)\) 次,但位于 \(i\) 左边的超过 \(K\) 个时就会停止,所以时间复杂度不太好分析,大约为 \(O(k\sim n)\) 。我并没有特意去卡这种做法,只造了我自己的代码过不了的数据,或许卡下常能多水过一个点?

时间复杂度:\(O(nlogn+(nk \sim n^2)+nk)\)

分数:\(25pt \thicksim 40pt\)

【Code #2-1】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+5,M=1000+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,A[N],S[N],B1[M],B2[M],nex[N],Ans[M];
struct QAQ{
    LL x,v;QAQ(LL X=0,LL V=0){x=X,v=V;}
    inline bool operator<(const QAQ &O)const{return v!=O.v?v<O.v:x<O.x;}
}b[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int merge(Re *P,Re *A,Re t1,Re *B,Re t2){//归并
    Re t=0,p1=1,p2=1;A[t1+1]=B[t2+1]=-inf;
    while(t<K&&(p1<=t1||p2<=t2))//注意t>=K时要停止
        if(A[p1]>B[p2])P[++t]=A[p1++];
        else P[++t]=B[p2++];
    return t;
}
inline void out(){
    printf("%lld\n",Ans[K]);
    if(op){
        LL ans=0;
        for(Re i=1;i<=K;++i)(ans+=(Ans[i]+P)%P)%=P;
        printf("%lld\n",ans);
    }
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1;i<=n;++i)in(A[i]),S[i]=S[i-1]+A[i];
    for(Re i=0;i<=n;++i)b[++m]=QAQ(i,S[i]);
    sort(b+1,b+m+1);//从小到大排序
    nex[b[m].x]=n+1;//注意边界
    for(Re i=m-1;i>=1;--i)nex[b[i].x]=b[i+1].x;
    for(Re i=1;i<=n;++i){
        Re t1=0,t2=Ans[0];Ans[0]=0;
        for(Re j=b[1].x;t1<K&&j!=n+1;j=nex[j])
            if(j<i)B1[++t1]=S[i]-S[j];//从大到小存进去
        for(Re j=1;j<=t2;++j)B2[j]=Ans[j];
        Ans[0]=merge(Ans,B1,t1,B2,t2);
    }
    out();
}

【优化】

考虑去掉上面那个 \(O(\frac{n^2}{\text{玄学}})\) 的指针转移。

对于一个 \(i\),只要求出了它左边前 \(K\) 小的 \(S[j]\),就能将其变为前 \(K\) 大的 \(S[i]-S[j]\) 。前面使用 \(B_1\) 存了区间权值,那么如果我们用它来存 \(S[j]\) 呢?

\(i\) 向后移一位时,我们发现 \(B_{1}\) 中的大部分元素都没变,只是多出了一个 \(S[i-1]\),可以通过枚举或者二分找到我们需要插♂入的位置,然后将它塞进去。

上述操作均可 \(O(k)\) 实现。

时间复杂度:\(O(nk)\)

分数:\(40pt\)

【Code #2-2】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+5,M=1000+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,t1,t2,A[N],S[N],Q[M],B1[M],B2[M],nex[N],Ans[M];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int merge(Re *P,Re *A,Re t1,Re *B,Re t2){//归并
    Re t=0,p1=1,p2=1;A[t1+1]=B[t2+1]=-inf;
    while(t<K&&(p1<=t1||p2<=t2))
        if(A[p1]>B[p2])P[++t]=A[p1++];
        else P[++t]=B[p2++];
    return t;
}
inline void out(){
    printf("%lld\n",Ans[K]);
    if(op){
        LL ans=0;
        for(Re i=1;i<=K;++i)(ans+=(Ans[i]+P)%P)%=P;
        printf("%lld\n",ans);
    }
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1;i<=n;++i)in(A[i]),S[i]=S[i-1]+A[i];
    for(Re i=1;i<=n;++i){
        if(t1<K)Q[++t1]=inf;//如果还没有K个,增加一个位置
        for(Re j=1;j<=t1;++j)
            if(S[i-1]<Q[j]){//找到需要插♂入的位置
                for(Re k=t1-1;k>=j;--k)Q[k+1]=Q[k];//后面整体向后移一位腾出位置
                Q[j]=S[i-1];break;//插♂入S[i-1]
            }
        Re t2=Ans[0];
        for(Re j=1;j<=t1;++j)B1[j]=S[i]-Q[j];
        for(Re j=1;j<=t2;++j)B2[j]=Ans[j];
        Ans[0]=merge(Ans,B1,t1,B2,t2);
    }
    out();
}

【Solution #3】

特殊性质 \(1\)\(\forall i \in [1,n],\) \(a_{i} \geqslant 0\)

\(a_{i}\) 均大于 \(0\),有什么用?

任意一个区间的权值都一定不会比它的子区间权值小。

最大的区间一定是 \([1,n]\),先把 \([1,n]\) 丢进一个按区间权值排序的大根堆。不断取堆顶的元素直至 \(K\) 次,然后将取出来的区间 \([l,r]\) 变成 \([l,r-1]\)\([l+1,r]\) 丢进去。

注意 \([l+1,r]\)\([l,r-1]\) 都有可能产生 \([l+1,r-1]\),所以每次加入时都要判断该区间是否已经被加过。

时间复杂度:\(O(KlogK)\)

分数:\(10pt\),结合之前的算法可以得到 \(50pt\)

【Code #3-1】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define LL long long
#define Re register LL
#define mp make_pair
using namespace std;
const LL N=1e5+6,M=1e6+5,inf=1e18,P=99999999999999997ll;
LL n,K,op,flag=1,A[N],S[N],Ans[M];
struct QAQ{
    LL l,r,v;QAQ(LL L=0,LL R=0){v=S[r=R]-S[(l=L)-1];}
    inline bool operator<(const QAQ &O)const{return v<O.v;}
};
priority_queue<QAQ>Q;map<pair<LL,LL>,bool>pan;
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void sakura(){//求前K大
    Q.push(QAQ(1,n));
    while(Ans[0]<K){
        QAQ x=Q.top();Q.pop();
        Ans[++Ans[0]]=x.v;
        if(x.l!=x.r){
            if(!pan[mp(x.l,x.r-1)])pan[mp(x.l,x.r-1)]=1,Q.push(QAQ(x.l,x.r-1));
            if(!pan[mp(x.l+1,x.r)])pan[mp(x.l+1,x.r)]=1,Q.push(QAQ(x.l+1,x.r));
        }
    }
}
inline void out(){
    printf("%lld\n",Ans[K]);
    if(op){
        LL ans=0;
        for(Re i=1;i<=K;++i)(ans+=(Ans[i]+P)%P)%=P;
        printf("%lld\n",ans);
    }
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1;i<=n;++i)in(A[i]),S[i]=S[i-1]+A[i],flag&=(A[i]>=0);
    if(flag){sakura(),out();return 0;}
}

【算法改进】

考虑上述算法如何一般化。

类似 超级钢琴 \(\text{[NOI2010] [P2048]}\),用大根堆维护四元组 \(\{x,l,r,t\}\),其中 \(t\) 为区间 \([l,r]\) 中最大前缀和 \(S\) 的位置,可以用 \(st\) 表预处理。堆按照 \(S[t]-S[x]\) 的大小排序。

对于每个 \(i\),将 \(\{i,i,n,t\}\) 加入大根堆,堆按照 \(S[t]-S[x]\) 的大小排序。每次取出堆顶元素,然后将取出来的区间 \([l,r]\) 变为 \([l,t-1]\)\([t+1,r]\) 重新加进去。

代码超简单。

时间复杂度:\(O(nlogn+KlogK)\)

分数:\(70pt\)

【Code #3-2】
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const LL N=1e5+5,M=1e6+5,logN=17,inf=1e18,P=99999999999999997ll;
int n,K,op,A[N],lg[N],f[N][18];LL S[N],Ans[M];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int Max(Re a,Re b){return S[a]>S[b]?a:b;}
inline int ask(Re L,Re R){Re m=lg[R-L+1];return Max(f[L][m],f[R-(1<<m)+1][m]);}
struct QAQ{
    int x,t,l,r;QAQ(int X=0,int L=0,int R=0){x=X,t=ask(l=L,r=R);}
    inline bool operator<(const QAQ &O)const{return S[t]-S[x-1]<S[O.t]-S[O.x-1];}
};
priority_queue<QAQ>Q;
inline void out(){
    printf("%lld\n",Ans[K]);
    if(op){
        LL ans=0;
        for(Re i=1;i<=K;++i)(ans+=(Ans[i]+P)%P)%=P;
        printf("%lld\n",ans);
    }
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op),lg[0]=-1;
    for(Re i=1;i<=n;++i)in(A[i]),S[f[i][0]=i]=S[i-1]+A[i],lg[i]=lg[i>>1]+1;
    for(Re j=1;j<=logN;++j)
        for(Re i=1;i+(1<<j)-1<=n;++i)
            f[i][j]=Max(f[i][j-1],f[i+(1<<j-1)][j-1]);
    for(Re i=1;i<=n;++i)Q.push(QAQ(i,i,n));
    while(Ans[0]<K){
        QAQ x=Q.top();Q.pop();
        Ans[++Ans[0]]=S[x.t]-S[x.x-1];
        if(x.t!=x.l)Q.push(QAQ(x.x,x.l,x.t-1));
        if(x.t!=x.r)Q.push(QAQ(x.x,x.t+1,x.r));
    }
    out();
}

【Solution #4】

特殊性质 \(2\):只需要求输出 \(K\) 大区间,不用输出前 \(K\) 大区间的权值和。

求第 \(K\) 大可以用二分啊!

二分一个 \(limit\),每次计算权值大于等于 \(limit\) 的区间个数,如果大于等于 \(K\) 就调下界,否则调上界。

\(judge\) 函数随便弄个什么数据结构就搞过去了,而且还是很明显的 \(\text{CDQ}\) 板子,但同样的复杂度,离散化 \(+\) 树状数组要快一倍。

注意写树状数组时不能在 \(judge\) 函数中进行离散化,否则会平白无故多出一个 \(O(logn)\),由于 \(S\) 数组均减去(加上)\(limit\) 后排名关系不变,所以可以提前预处理处 \(S\) 的排名,以此获得 \(S\) 减去(加上) \(limit\) 后的排名,然后用归并将两部分合起来得到最终使用的离散排名。

时间复杂度:\(O(nlognloginf)\)

分数:\(10pt\),结合之前的算法可以得到 \(80pt\)

【Code #4-1】

(这是树状数组的写法,\(\text{CDQ}\) 做法与下面的 \(\text{Code #4-3}\) 放一起了)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e6+5,M=1e6+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,Ans0,Max,Min,A[N],S[N],rk[N][2];
struct QAQ{
    LL i,S,rk;bool o;
    inline bool operator<(const QAQ &O)const{return S<O.S;}
}S1[N],S2[N],S0[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct BIT{
    LL n,C[N<<1];
    inline void CL(Re m){n=m;for(Re i=1;i<=n;++i)C[i]=0;}
    inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}
    inline LL ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}
}T1;
inline LL merge_(QAQ *P,QAQ *A,Re t1,QAQ *B,Re t2){//归并
    Re t=0,t_=0,p1=1,p2=1;A[t1+1].S=B[t2+1].S=inf;
    while((p1<=t1||p2<=t2))
        if(A[p1].S<B[p2].S)P[++t]=A[p1++];
        else P[++t]=B[p2++];
    for(Re i=1;i<=t;++i){
        if(i<2||P[i].S!=P[i-1].S)++t_;
        P[i].rk=t_;
    }
    return t_;
}
inline LL judge(Re mid){
    for(Re i=1;i<=m;++i)S2[i]=S1[i],S2[i].S-=mid,S2[i].o=1;//S1本身有序,S1.S[i]均减去mid后依然有序
    Re tmp=0,t_=merge_(S0,S1,m,S2,m);T1.CL(t_);//把两个有序的S1,S2合成一个S0,可以直接归并
    for(Re i=1;i<=(m<<1);++i)rk[S0[i].i][S0[i].o]=S0[i].rk;//获得排名
    for(Re i=1;i<=n&&tmp<K;++i)T1.add(rk[i-1][0]),tmp+=T1.ask(rk[i][1]);//统计大于等于mid的区间数量
    return tmp>=K;
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1,Mi=0,Ma=0;i<=n;++i)
        in(A[i]),S[i]=S[i-1]+A[i],
        Max=max(Max,S[i]-Mi),Mi=min(Mi,S[i]),
        Min=min(Min,S[i]-Ma),Ma=max(Ma,S[i]);
    for(Re i=0;i<=n;++i)S1[++m]=(QAQ){i,S[i]};
    sort(S1+1,S1+m+1);//让S1有序
    Re l=Min,r=Max;//卡一下常数
    while(l<r){
        Re mid=l+r+1>>1;
        if(judge(mid))l=mid;
        else r=mid-1;
    }
    printf("%lld\n",Ans0=l);
}

【算法改进】

解决了特殊性质 \(2\),正解自然就出来了。

先算出第 \(K\) 大权值 \(Ans_0\)

依旧是上 \(\text{CDQ}\) 板子:计算权值严格大于 \(Ans_0\) 的区间权值和。

最后再加上 \(Ans_0\) \(\times\) \((K-\) 权值大于等于 \(Ans_0+1\) 的区间个数 \()\)

时间复杂度:\(O(nlognloginf+nlogn)\)

分数:\(100pt\)

【Code #4-2】

(树状数组 \(judge\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,Max,Min,Ans0,Ans1,A[N],S[N],S_[N],rk[N][2];
struct QAQ{
    LL i,S,rk;bool o;
    inline bool operator<(const QAQ &O)const{return S<O.S;}
}S1[N],S2[N],S0[N<<1];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct BIT{
    LL n,C[N<<1];
    inline void CL(Re m){n=m;for(Re i=1;i<=n;++i)C[i]=0;}
    inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}
    inline LL ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}
}T1;
inline LL merge_(QAQ *P,QAQ *A,Re t1,QAQ *B,Re t2){//归并
    Re t=0,t_=0,p1=1,p2=1;A[t1+1].S=B[t2+1].S=inf;
    while((p1<=t1||p2<=t2))
        if(A[p1].S<B[p2].S)P[++t]=A[p1++];
        else P[++t]=B[p2++];
    for(Re i=1;i<=t;++i){
        if(i<2||P[i].S!=P[i-1].S)++t_;
        P[i].rk=t_;
    }
    return t_;
}
inline LL judge(Re mid){
    for(Re i=1;i<=m;++i)S2[i]=S1[i],S2[i].S-=mid,S2[i].o=1;//S1本身有序,S1.S[i]均减去mid后依然有序
    Re tmp=0,t_=merge_(S0,S1,m,S2,m);T1.CL(t_);//把两个有序的S1,S2合成一个S0,可以直接归并
    for(Re i=1;i<=(m<<1);++i)rk[S0[i].i][S0[i].o]=S0[i].rk;//获得排名
    for(Re i=1;i<=n&&tmp<K;++i)T1.add(rk[i-1][0]),tmp+=T1.ask(rk[i][1]);//统计大于等于mid的区间数量
    return tmp;
}
inline void merge(Re *P,Re p1,Re t1,Re p2,Re t2){//归并
    Re t=p1-1;
    while((p1<=t1||p2<=t2))
        if((p1<=t1&&S[p1]<S[p2])||p2>t2)P[++t]=S[p1++];//注意判断是否大于t1,t2
        else P[++t]=S[p2++];
}
inline void CDQ(Re L,Re R){
    if(L==R)return;
    Re mid=L+R>>1,p1=L-1,p2=mid+1,SL=0;
    CDQ(L,mid),CDQ(mid+1,R);
    while(p2<=R){
        while(p1<mid&&S[p2]-S[p1+1]>Ans0)SL+=S[++p1];//移左指针
        (Ans1+=(S[p2++]*(p1-L+1)-SL+P)%P)%=P;//右指针一位一位地移
    }
    merge(S_,L,mid,mid+1,R);//归并排序
    for(Re i=L;i<=R;++i)S[i]=S_[i];
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1,Mi=0,Ma=0;i<=n;++i)
        in(A[i]),S[i]=S[i-1]+A[i],
        Max=max(Max,S[i]-Mi),Mi=min(Mi,S[i]),
        Min=min(Min,S[i]-Ma),Ma=max(Ma,S[i]);
    for(Re i=0;i<=n;++i)S1[++m]=(QAQ){i,S[i]};
    sort(S1+1,S1+m+1);//让S1有序
    Re l=Min,r=Max;//卡一下常数
    while(l<r){
        Re mid=l+r+1>>1;
        if(judge(mid)>=K)l=mid;
        else r=mid-1;
    }
    printf("%lld\n",Ans0=l);
    if(op){
        K-=judge(Ans0+1),CDQ(0,n);
        printf("%lld\n",(Ans1+K*Ans0%P)%P);//其实K*Ans0是有可能会炸的(10^19),懒得写龟速乘和专门出数据了
    }
}
【Code #4-3】

\(\text{CDQ}\) \(judge\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register LL
using namespace std;
const LL N=1e5+5,inf=1e18,P=99999999999999997ll;
LL n,m,K,op,tmp,Max,Min,Ans0,Ans1,A[N],S[N],S_[N],S__[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void merge_(Re *P,Re p1,Re t1,Re p2,Re t2){//归并
    Re t=p1-1;
    while((p1<=t1||p2<=t2))
        if((p1<=t1&&S_[p1]<S_[p2])||p2>t2)P[++t]=S_[p1++];//注意判断是否大于t1,t2
        else P[++t]=S_[p2++];
}
inline void CDQ_(Re L,Re R,Re limit){
    if(L==R)return;
    Re mid=L+R>>1,p1=L-1,p2=mid+1;
    CDQ_(L,mid,limit),CDQ_(mid+1,R,limit);
    while(p2<=R){
        while(p1<mid&&S_[p2]-S_[p1+1]>=limit)++p1;//移左指针
        tmp+=p1-L+1,++p2;//右指针一位一位地移
    }
    merge_(S__,L,mid,mid+1,R);//归并排序
    for(Re i=L;i<=R;++i)S_[i]=S__[i];
}
inline LL judge(Re mid){
    tmp=0;
    for(Re i=0;i<=n;++i)S_[i]=S[i];
    CDQ_(0,n,mid);
    return tmp;
}
inline void merge(Re *P,Re p1,Re t1,Re p2,Re t2){//归并
    Re t=p1-1;
    while((p1<=t1||p2<=t2))
        if((p1<=t1&&S[p1]<S[p2])||p2>t2)P[++t]=S[p1++];//注意判断是否大于t1,t2
        else P[++t]=S[p2++];
}
inline void CDQ(Re L,Re R){
    if(L==R)return;
    Re mid=L+R>>1,p1=L-1,p2=mid+1,SL=0;
    CDQ(L,mid),CDQ(mid+1,R);
    while(p2<=R){
        while(p1<mid&&S[p2]-S[p1+1]>Ans0)SL+=S[++p1];//移左指针
        (Ans1+=(S[p2++]*(p1-L+1)-SL+P)%P)%=P;//右指针一位一位地移
    }
    merge(S_,L,mid,mid+1,R);//归并排序
    for(Re i=L;i<=R;++i)S[i]=S_[i];
}
int main(){
//  freopen("data.in","r",stdin);
    in(n),in(K),in(op);
    for(Re i=1,Mi=0,Ma=0;i<=n;++i)
        in(A[i]),S[i]=S[i-1]+A[i],
        Max=max(Max,S[i]-Mi),Mi=min(Mi,S[i]),
        Min=min(Min,S[i]-Ma),Ma=max(Ma,S[i]);
    Re l=Min,r=Max;//卡一下常数
    while(l<r){
        Re mid=l+r+1>>1;
        if(judge(mid)>=K)l=mid;
        else r=mid-1;
    }
    printf("%lld\n",Ans0=l);
    if(op){
        K-=judge(Ans0+1),CDQ(0,n);
        printf("%lld\n",(Ans1+K*Ans0%P)%P);//其实K*Ans0是有可能会炸的(10^19),懒得写龟速乘和专门出数据了
    }
}

【一些题外话】

感谢 \(\text{YudeS}\) 提出 \(\text{Code #3-2}\) 中的做法。

这道题是在考试期间闲得无聊 \(yy\) 出来的。

posted @ 2020-01-09 22:02  辰星凌  阅读(484)  评论(0编辑  收藏  举报