HDU 3473 Minimum Sum【划分树】


http://acm.hdu.edu.cn/submit.php?pid=3473
HDU 3473 Minimum Sum
大意:已知n个正整数x0,x1,x2...xn-1排成一列,要求回答若干问题,
问题类型如下:
给你一个区间[l,r],要求sum(x-xi)(l<=i<=r)的最小值,其中x必须为xl,xl+1...xr中的一个数

分析:易推得对于任意区间[l,r],符合条件的x为所在区间的中位数,求任意区间的中位数可以用划分树
来解决,同样的,我们用suml[deep][i]来记录划分树中第deep层到数i位置放入左子树的数字的和,
注意:当最后得出中位数median左边的和suml及中位数右边的和sumr,以及它们所对应的个数lnum,rnum,
此时ans = sumr - suml +(lnum-rnum)*ave,切不可左右分开求,否者数据可能超过__int64,我就死在这里了,纠结了整整一下午。。~~~~(>_<)~~~~

View Code
   
     
#include < stdio.h >
#include
< iostream >
#include
< string .h >
#include
< algorithm >
using namespace std;
const int MAXN = 100000 + 10 ;
typedef __int64 LL;
long tree[ 22 ][MAXN],toleft[ 22 ][MAXN];
LL lsum[
22 ][MAXN]; // lsum[deep][i]为第deep层到第xi被划入左子树的数字的和
LL sum[MAXN]; // sum[i]为1。。i的和
long sorted[MAXN]; // 已经排好序的数据
LL suml;
int lnum;
void build_tree( int left, int right, int deep) // 建树
{
if (left == right) return ;
int mid = (left + right) >> 1 ;
int i;
int same = mid - left + 1 ; // 位于左子树的数据
for (i = left;i <= right;i ++ ) // 计算放于左子树中与中位数相等的数字个数
{
if (tree[deep][i] < sorted[mid])
same
-- ;
}

int ls = left;
int rs = mid + 1 ;
for (i = left;i <= right;i ++ )
{
int flag = 0 ;
if ((tree[deep][i] < sorted[mid]) || (tree[deep][i] == sorted[mid] && same > 0 )) // 放入左子树
{
flag
= 1 ;
tree[deep
+ 1 ][ls ++ ] = tree[deep][i];
if (tree[deep][i] == sorted[mid])same -- ;
lsum[deep][i]
= lsum[deep][i - 1 ] + tree[deep][i];
}
else // 右子树
{
tree[deep
+ 1 ][rs ++ ] = tree[deep][i];
lsum[deep][i]
= lsum[deep][i - 1 ];
}

toleft[deep][i]
= toleft[deep][i - 1 ] + flag;
}

build_tree(left,mid,deep
+ 1 );
build_tree(mid
+ 1 ,right,deep + 1 );
}

int query( int left, int right, int k, int L, int R, int deep)
{
if (left == right) return tree[deep][left];
int mid = (L + R) >> 1 ;
int x = toleft[deep][left - 1 ] - toleft[deep][L - 1 ]; // 位于left左边的放于左子树中的数字个数
int y = toleft[deep][right] - toleft[deep][L - 1 ]; // 到right为止位于左子树的个数
int ry = right - L - y; // 到right右边为止位于右子树的数字个数
int cnt = y - x; // [left,right]区间内放到左子树中的个数
int rx = left - L - x; // left左边放在右子树中的数字个数
if (cnt >= k)
return query(L + x,L + y - 1 ,k,L,mid,deep + 1 );
else
{
lnum
= lnum + cnt;
suml
= suml + lsum[deep][right] - lsum[deep][left - 1 ];
return query(mid + rx + 1 ,mid + 1 + ry,k - cnt,mid + 1 ,R,deep + 1 );
}

}




int main()
{
// freopen("E:/MYACM/Match/UESTC-4/Minimum Sum/1.in","r",stdin);
// freopen("2.out","w",stdout);
int n,m;
int T;
int cases = 0 ;
while (scanf( " %d " , & T) != EOF)
{
cases
= 0 ;
while (T -- )
{
cases
++ ;
scanf(
" %d " , & n);
int i;
for (i = 0 ;i < 20 ;i ++ )
lsum[i][
0 ] = 0 ;
sum[
0 ] = 0 ;
for (i = 1 ;i <= n;i ++ )
{
scanf(
" %d " , & sorted[i]);
tree[
0 ][i] = sorted[i];
sum[i]
= sum[i - 1 ] + sorted[i];
}

sort(sorted
+ 1 ,sorted + n + 1 );
build_tree(
1 ,n, 0 );

scanf(
" %d " , & m);
printf(
" Case #%d:\n " ,cases);
while (m -- )
{
int left,right,k;
scanf(
" %d%d " , & left, & right);
left
++ ;
right
++ ;
k
= ((right - left) / 2 ) + 1 ;
suml
= 0 ;
lnum
= 0 ;
long ave = query(left,right,k, 1 ,n, 0 ); // 求中位数
int rnum = (right - left + 1 - lnum);

LL sumr
= sum[right] - sum[left - 1 ] - suml; // 右子树的和
LL ans = sumr - ave * (rnum - lnum) - suml;
printf(
" %I64d\n " ,ans);
}
printf(
" \n " );
}

}
return 0 ;
}

你可能感兴趣的:(HDU)