HDU 3473 Minimum Sum 划分树

题意:

给出一个长度为\(n(1 \leq n \leq 10^5)\)的序列\(a\)
有若干次查询l r:找到一个\(x\)使得\(\sum \limits_{l \leq i \leq r} \left | x-a_i \right |\)的值最小。

分析:

有这样一个结论:\(x\)为子序列的中位数时差的绝对值之和最小。
证明也很简单:

将序列中的每个元素对应到数轴上的点,\(x\)是数轴上一个动点。
\(x\)左边有\(l\)个点,右边有\(r\)个点。
如果动点向右移动\(\Delta x\)距离(而且保证移动后左右两侧点数不变),那么目标值就会变化\(l \Delta x - r \Delta x\)
如果\(l<r\),这个值会变小;如果\(l>r\),那么向左移动这个值会变小。
直到左右两侧点数相等。

对于这道题就可以很方便地计算出答案:计算出中位数的大小\(mid\),中位数左右两侧数字的个数\(cnt_l,cnt_r\)以及的对应的和\(sum_l,sum_r\)
最终答案就是:\((mid \cdot cnt_l - sum_l) + (sum_r - mid \cdot cnt_r)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
const int maxn = 100000 + 10;
const int maxd = 20;

int n;
int sorted[maxn];

int T[maxd][maxn], cnt[maxd][maxn];
LL sum[maxd][maxn], pre[maxn];

void build(int d, int L, int R) {
    int M = (L + R) / 2;
    int lsame = M - L + 1;
    for(int i = L; i <= R; i++)
        if(T[d][i] < sorted[M]) lsame--;
    int lpos = L, rpos = M + 1;
    for(int i = L; i <= R; i++) {
        if(i == L) { sum[d][i] = 0; cnt[d][i] = 0; }
        else { sum[d][i] = sum[d][i-1]; cnt[d][i] = cnt[d][i-1]; }
        if(T[d][i] < sorted[M] || (T[d][i] == sorted[M] && lsame)) {
            cnt[d][i]++;
            sum[d][i] += T[d][i];
            T[d+1][lpos++] = T[d][i];
            if(T[d][i] == sorted[M]) lsame--;
        } else T[d+1][rpos++] = T[d][i];
    }

    if(L < M) build(d + 1, L, M);
    if(M + 1 < R) build(d + 1, M + 1, R);
}

LL q_kth, q_sum;

void query(int d, int L, int R, int qL, int qR, int k) {
    if(L == R) { q_kth = T[d][L]; q_sum += T[d][L]; return; }
    int M = (L + R) / 2;
    int numl;
    if(qL == L) numl = 0;
    else numl = cnt[d][qL - 1];
    int numr = cnt[d][qR];
    int num = numr - numl;
    if(num >= k) {
        query(d + 1, L, M, L + numl, L + numr - 1, k);
    } else {
        LL suml;
        if(qL == L) suml = 0;
        else suml = sum[d][qL - 1];
        q_sum += sum[d][qR] - suml;
        numl = qL - L - numl;
        numr = qR - L + 1 - numr;
        query(d + 1, M+1, R, M+1+numl, M+numr, k - num);
    }
}

int main()
{
    int _; scanf("%d", &_);
    for(int kase = 1; kase <= _; kase++) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", sorted + i);
            pre[i] = pre[i - 1] + sorted[i];
            T[0][i] = sorted[i];
        }
        sort(sorted + 1, sorted + 1 + n);
        build(0, 1, n);

        printf("Case #%d:\n", kase);
        int q; scanf("%d", &q);
        while(q--) {
            int l, r; scanf("%d%d", &l, &r);
            l++; r++;
            int k = (r - l) / 2 + 1;
            q_sum = 0;
            query(0, 1, n, l, r, k);
            LL ans = q_kth * k - q_sum;
            ans += (pre[r] - pre[l-1] - q_sum) - q_kth * (r - l + 1 - k);
            printf("%lld\n", ans);
        }
        printf("\n");
    }

    return 0;
}

转载于:https://www.cnblogs.com/AOQNRMGYXLMV/p/5346676.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值