给你n,再给你n个数,最后给一个k
求出这个序列的第k大连续区间和; (注意这里重复出现的数字只被统计一次)
N(1<=N<=50000)
ai(1<=ai<=100)
K(1<=K<=(n+1)*n/2).
思路:
预处理前缀和。并将其离散化(去重)。
二分答案,然后每次判断中,遍历前缀和Bi,然后查找有多少个j<i ,满足 Bi-Bj>X,也就是有多少个子序列的和是大于X的,即使求有多少个Bj小于【Bi-X】,这部分查询我们可以用树状数组实现,方法类似于树状数组求逆序对。
查询方法简要说一下,也就是当遍历到Bi的时候,我们找到【Bi-ans】在离散化后在树状数组对应的下标Y,然后查询get(1,Y)看之前出现过的Bi有多少个在这个范围,然后ret+=这部分,最后把Bi插入到树状数组中
复杂度分析: 外层二分是logMAXX咯,然后每次nlogn
所以总的复杂度是nlognlogMAXX,MAXX等于区间和的上界
【主席树的做法见最后面】
二分参考代码:
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #include <set> #include <vector> #include <iostream> using namespace std; const double pi=acos(-1.0); double eps=0.000001; vector<int> dis; int tm[500005]; int sum[500005]; int tree[500005]; int n,k; int lowbit(int x) { return x&-x;} void add(int x,int value) { for (int i=x;i<=n;i=i+lowbit(i)) { tree[i]+=value; } } int get(int x) { int sum=0; for (int i=x;i;i-=lowbit(i)) { sum+=tree[i]; } return sum; } int bin(int mid) //求多少个子序列的和严格大于mid { int i,ret=0; memset(tree,0,sizeof(tree)); for (i=1;i<=n;i++) { int x=sum[i]-mid; //need: sum[i]-sum[j]>mid,==> sum[i]-mid>sum[j],对应s[j+1]...s[i] int it=lower_bound(dis.begin(),dis.end(),x)-dis.begin(); //找到离散化数组中对应下标 ret+=get(it); //求得J的个数 if (x>0) //前缀和=子段和的情况 ret++; it=lower_bound(dis.begin(),dis.end(),sum[i])-dis.begin(); add(it+1,1); //插入Bi } return ret; } int main() { int i,j; int t;cin>>t; while(t--) //t组样例 { scanf("%d",&n); for (i=1;i<=n;i++) scanf("%d",&tm[i]); scanf("%d",&k); dis.clear(); for (i=1;i<=n;i++) { sum[i]=sum[i-1]+tm[i]; dis.push_back(sum[i]); } sort(dis.begin(),dis.end()); dis.erase(unique(dis.begin(),dis.end()),dis.end()); //去重 int maxx=500000*105; int minn=-500000*105; int l=minn,r=maxx; int ans=0; while(l<=r) //二分逼近答案 { int mid=(l+r)>>1; if (bin(mid)>=k) l=mid+1; else r=mid-1,ans=mid; } printf("ans:%d\n",ans); } return 0; }
主席树加堆的代码(复杂度klogn,适用于k较小的情况)
同样是求出前缀和sum【】,去重离散化,用idx【】建主席树
step1:把每个si对应找到最小的sj 丢进最大堆。
step2:每次取出堆顶Si,便是当前最大的区间和。
step3:然后找到堆顶元素的Si对应的次小的sj丢进堆里
重复step2,3 k次,得到第k大
证明:显然每次取出来的都是最大的区间和,而对取出来的是【si,sj】(sj是对应于si的第p小的数),那么下次只要找一个 si对应的 第 p+1小的数即可。。。这部分就用到主席树,每次在区间【1,idx【si】-1】找到 第p+1小的sj
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #include <set> #include <vector> #include <iostream> using namespace std; const int MAXNN=100005; #define w(i) T[(i)].w #define ls(i) T[(i)].ls #define rs(i) T[(i)].rs int min(int a,int b) {return a<b?a:b;} struct node { int ls,rs,w; node(){ls=rs=w=0;} }T[MAXNN*20]; int root[MAXNN],sz ; void insert(int &i,int l,int r,int x) { T[++sz]=T[i]; i=sz; w(i)++; if (l==r) return; int m=(l+r)>>1; if (x<=m) insert(ls(i),l,m,x); else insert(rs(i),m+1,r,x); } int query(int i,int j,int l,int r,int k) { if (l==r) return l; int t=w(ls(j))-w(ls(i)); int m=(l+r)>>1; if (t>=k) return query(ls(i),ls(j),l,m,k); else return query(rs(i),rs(j),m+1,r,k-t); } struct point { int i,j_k,val; point(){} point(int a,int b,int c){i=a,j_k=b;val=c;} bool operator<(const point &b)const {return val<b.val;} } ; priority_queue<point> q; int tm[50005]; int sum[50005]; int b[50005]; int idx[50005]; int main() { freopen( "F:\\duipai\\1.in","r",stdin ); freopen("F:\\duipai\\1.out","w",stdout); //DO YOLO int t;cin>>t; while(t--) //t组样例 { root[0]=0; //init 主席树 sz=0; while(!q.empty())q.pop(); int n; scanf("%d",&n); int i; for (i=2;i<=n+1;i++) scanf("%d",&tm[i]); for (i=2;i<=n+1;i++) sum[i]=sum[i-1]+tm[i]; b[1]=0; //由于前缀和的可以选sj-0为子段,所以插入一个0方便统计 for (i=2;i<=n+1;i++) b[i]= sum[i] ; n++; sort(b+1,b+1+n); int size=unique(b+1,b+1+n)-b-1; //去重离散化 for (i=1;i<=n;i++) //build 主席树 { root[i]=root[i-1]; idx[i]=lower_bound(b+1,b+1+size,sum[i])-b; insert(root[i],1,n,idx[i]); } for (i=2;i<=n;i++) //初始化堆 { int ret=query(root[0],root[i-1],1,n,1); //min_j_index int minj=b[ret]; q.push(point(i,1,sum[i]-minj)); } int ans=0; int k; scanf("%d",&k); while(k--) { point tt=q.top();q.pop(); ans=tt.val; int lastk=tt.j_k; //上次第lastk小 int lasti=tt.i; //上次的si if (lastk+1>lasti-1) continue; //该si没有sj可以取了 int ret=query(root[0],root[lasti-1],1,n,lastk+1); //min_j_index int minj=b[ret]; q.push(point(lasti,lastk+1,sum[lasti]-minj)); } printf("%d\n",ans); } return 0; }