您的位置:首页 > 其它

hdu 3473 Minimum Sum 划分树的应用

2015-08-04 09:32 309 查看
链接:http://acm.hdu.edu.cn/showproblem.php?pid=3473


Minimum Sum

Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 3427 Accepted Submission(s): 787



Problem Description

You are given N positive integers, denoted as x0, x1 ... xN-1. Then give you some intervals [l, r]. For each interval, you need to find a number x to make

as small as possible!



Input

The first line is an integer T (T <= 10), indicating the number of test cases. For each test case, an integer N (1 <= N <= 100,000) comes first. Then comes N positive integers x (1 <= x <= 1,000, 000,000) in the next line. Finally, comes an integer Q (1 <=
Q <= 100,000), indicting there are Q queries. Each query consists of two integers l, r (0 <= l <= r < N), meaning the interval you should deal with.



Output

For the k-th test case, first output “Case #k:” in a separate line. Then output Q lines, each line is the minimum value of

. Output a blank line after every test case.



Sample Input

2

5
3 6 2 2 4
2
1 4
0 2

2
7 7
2
0 1
1 1




Sample Output

Case #1:
6
4

Case #2:
0
0




题意:

问l,r区间 取任意整数x , 问 ∑|X-xi| 的最小值

做法:很明显 这个X是中位数。 中位数想到划分树,然后在划分树 建树的时候,把进左子树的数计算一个前缀和,然后在查询的时候,如果到右子树,就把区间内进入左子树的数算一个和。最后统计出来的sumlft 就是所有比区间内中位数小的数了。

//O(log(n)) 找第k大的值
#include <iostream> 
#include <cstdio>
#include <algorithm>
using namespace std;

#define N 100009
#define M 19
//M>log2,N;
#define MID ((l+r)>>1)
#define ll __int64
int s
;//s存的是 sort后的原始数据;
int t[M]
;//t存的是树;
int numb[M]
;//和t数组同步,当前层,当前区间,N下标前有多少个数划归到左子树.
int n,m; 
ll quzuo[M]
;
ll sum
; 
void Build(int c,int l,int r) //c是层数
{
	int lm=MID-l+1,lp=l,rp=MID+1; //lp左边的起点  rp 右边的起点
	for(int i=l;i<=MID;i++)
		lm-=s[i]<s[MID]; //lm 代表 现在的左块(下标l-MID)中 有多少等于s[MID]的 包括其本身

	int zuo,you;
	for(int i=l;i<=r;i++)
	{
		zuo=you=0;
		if( i==l )
		{
			numb[c][i]=0; 
			quzuo[c][i]=0;
		}
		else
		{
			numb[c][i]=numb[c][i-1];
			quzuo[c][i]=quzuo[c][i-1];
		}
		if(t[c][i]==s[MID])
		{
			if( lm )//左边有多少等于中位数的的 都归到左块去
			{
				lm--;
				zuo=1;
			}
			else//如果 等于左块的中位数用完了, 就放到右块去 
				you=1; 
		}
		else if( t[c][i]<s[MID] )//小的去左块 
			zuo=1;  
		else 
			you=1; 

		if(zuo)
		{  
			numb[c][i]++;
			t[c+1][lp++]=t[c][i];
			quzuo[c][i]+=t[c][i]; 
		}
		else
		{  
			t[c+1][rp++]=t[c][i];
		}
	}
	if( l<r )
		Build(c+1,l,MID),Build(c+1,MID+1,r);
}

__int64 sumlft,sumrit;
int numlft,numrit;
int Query(int c,int l,int r,int ql,int qr,int k)//ql和qr是查询的区间左右边界,  l和r 当前区间.
{
	if( l==r )
		return t[c][l];
	int s,ss;
	if( l==ql )
		s=0,ss=numb[c][qr];
	else
		s=numb[c][ql-1],ss=numb[c][qr]-numb[c][ql-1];

	if( k<=ss )
	{  
		return Query(c+1,l,MID,l+s,l+s+ss-1,k);
	}
	else
	{
		if(ql==l)
		{ 
			sumlft+=quzuo[c][qr]-0;
		}
		else
		{ 
			sumlft+=quzuo[c][qr]-quzuo[c][ql-1];
		}
		return Query(c+1,MID+1,r,MID+1+ql-l-s,MID+1+qr-l-s-ss,k-ss);// ss到左子树 所以ss在右子树里排名下降ss
	}
} 
int main()
{

	int tt;
	int cas=1;
	scanf("%d",&tt);
	while(tt--)
	{
		scanf("%d",&n);
		sum[0]=0;
		for(int i=1;i<=n;i++)
		{ 
			s[i]=i;
			scanf("%d",&s[i]);
			t[0][i]=s[i];
			sum[i]=sum[i-1]+(ll)s[i];
		}
		sort(s+1,s+1+n);
		Build(0,1,n);
		scanf("%d",&m);

		printf("Case #%d:\n",cas++);
		while( m-- )
		{
			int l,r;
			sumlft=sumrit=numlft=numrit=0;//在进入右子树的时候,把区间内 到左子树的值加到sumlft中
			scanf("%d%d",&l,&r);// l-r 中 第k大,l从1开始 c=0开始,
			l++;
			r++;
			int ans;
			ans=Query(0,1,n,l,r,(r-l)/2+1);
			numlft=(r-l)/2;
			numrit=r-l-numlft;
			sumrit=sum[r]-sum[l-1]-sumlft-(ll)ans; 

			//  printf("zuohe %d youhe %d  zuoshu %d youshu %d ans%d \n",sumlft,sumrit,numlft,numrit,ans);
			printf("%I64d\n",((ll)numlft*(ll)ans-sumlft)+(sumrit-(ll)numrit*(ll)ans));
		}
		puts("");
	}
	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: