求第k大连续区间和/第k大子序列和 - 二分+树状数组+前缀和(或主席树+堆)

给你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;
}

猜你喜欢

转载自blog.csdn.net/viphong/article/details/50932056