【划分树】MinimumSum

MinimumSum

描述

已知含有N个正整数的数列 x0, x1 ... xN-1。对于每个询问的区间[l, r](包含左右端点),在区间内找出一个数x使得尽量小。

 

输入

每组数据中:

第一行是数列长度N(1 <= N <= 100,000)

第二行给出数列中的N个数xi (1 <= x <=1,000, 000,000)

第三行为询问数Q(1 <= Q <= 100,000),每个询问给出区间的左右端点l, r(0 <= l <= r < N)

 

输出

输出Q行,每一行是询问区间[l,r]的.最小值

输入样例1:

5

3 6 2 2 4

2

1 4

0 2

输出样例1:

6

4

 

输入样例2:

2

7 7

2

0 1

1 1

输出样例2:

0

0

 

数据规模

         30%的数据,N<=1000,Q<=1000。

         所有数据,N<=100000,Q<=100000,  1<= xi <= 1,000, 000,000,  0 <= l<= r < N








先反省一下,又是少开一个long long,结果就是调了整整一天



稍稍动点脑筋就可以知道我们选的基准 X 肯定是中位数!


好了,题目就转化成求第K大了,所以划分树


注意那个公式有绝对值,所以我们就不能简单的化简为| N*x - Sum(Xi,...,Xj) | ,为什么自己想想

所以我们要计算那个公式,必须一个一个算,极限情况O(N),所以还是要T,所以公式的代价我们需要降下来!

我们发现,虽然不能直接合并,但是可以把   A*x - Sum(比X小的) + Sum(比X大的) - B*x             [ A + B = N ]


所以方法有两个

①划分数+树状数组/线段树

这个说实话没写过,题解上说的方法,用划分树求第K大的数 X ,然后用线段树(树状数组)维护比 X 小的数的和,在维护比X大的数的和

②划分树

我就用的这个方法


在划分树的基础上再多维护一个sum,表示进入左儿子的的和,至于右儿子的和我们可以用前缀和算出总和减去左儿子的和

维护出来后在查询的时候额外维护处左儿子的和以及左儿子的个数


测评情况(cena)

①考试30分,写的朴素

②后来改了一个上午,结果是多维护的那个sum开的int,而应该开成long long



C++ AC Code

/*http://blog.csdn.net/jiangzh7
By Jiangzh*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using std::sort;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int N=100000+10;
const int inf=0x3f3f3f3f;
typedef long long LL;
int n,m;
int a[N];
LL sum[N];
int sorted[N];
struct node{int val,left;LL sum;}val[50][N];//一定要注意sum开long long啊!!!

void read()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
		sorted[i]=a[i];
		val[0][i].val=a[i];
	}
	sort(sorted+1,sorted+1+n);
}

void build_tree(int d,int l,int r)
{
	if(l==r) return;
	int m=(l+r)>>1;
	int same=m-l+1;
	int lcnt=l,rcnt=m+1;
	for(int i=l;i<=r;i++) if(val[d][i].val<sorted[m]) same--;
	for(int i=l;i<=r;i++)
	{
		int flag=0;
		if(val[d][i].val<sorted[m]||(val[d][i].val==sorted[m]&&same>0))
		{
			flag=1;
			if(val[d][i].val==sorted[m]) same--;
			val[d+1][lcnt++]=val[d][i];
		}
		else{
			val[d+1][rcnt++]=val[d][i];
		}
		val[d][i].left=val[d][i-1].left+flag;
		val[d][i].sum=val[d][i-1].sum+((flag)?val[d][i].val:0);//额外维护的sum
	}
	build_tree(d+1,l,m);
	build_tree(d+1,m+1,r);
}

int query(int d,int l,int r,int x,int y,int k,LL &lsum,LL &lnum)
{
	if(l==r) 
	{
		lsum=lnum=0;
		return val[d][l].val;
	}
	int m=(l+r)>>1;
	int lx=val[d][x-1].left-val[d][l-1].left;//[l,x-1] to left
	int ly=val[d][y].left-val[d][x-1].left;//[x,y] to left
	int rx=(x-1-l+1)-lx;//[l,x-1] to right
	int ry=(y-x+1)-ly;//[x,y] to right
	int res;
	if(ly>=k) res=query(d+1,l,m,l-1+lx+1,l-1+lx+ly,k,lsum,lnum);
	else{
		res=query(d+1,m+1,r,m+1-1+rx+1,m+1-1+rx+ry,k-ly,lsum,lnum);
		lnum+=ly;  lsum+=val[d][y].sum-val[d][x-1].sum;
		//要查找的第K大进入了右儿子,那么此时就可以累加上进入左儿子的信息
	}
	return res;
}

void work()
{
	build_tree(0,1,n);
	int m;scanf("%d",&m);
	while(m--)
	{
		int x,y;scanf("%d%d",&x,&y);x++;y++;
		LL lsum=0,lnum=0;
		LL rsum=sum[y]-sum[x-1],rnum=y-x+1;
		int k=((y-x+1)&1)?((y-x+1)/2+1):((y-x+1)/2);
		int sta=query(0,1,n,x,y,k,lsum,lnum);
		rsum-=lsum;rnum-=lnum;
		LL res=(LL)sta*lnum-lsum + rsum-(LL)sta*rnum;
		printf("%I64d\n",res);//linux下记得改成lld就行了
	}
}

int main()
{
	freopen("MinimumSum.in","r",stdin);
	freopen("MinimumSum.out","w",stdout);
	read();
	work();
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值