Codeforces Round #512 F. Putting Boxes Together 推导+树状数组+带权中位数

题意:

给定n个物品,每个物品有一个位置和权值,移动一个物品的花费是 移动距离*物品权值,

问:把一部分物品合并成相邻的一段,需要的最小花费

思路:

最优策略是某个物品不动,这个物品左边的依次往右移动,其右边的物品依次往左移动,(下文中的上述情况)

反证一下:假设最优情况下不动的这个物品往右移动了,那他和他左边的物品相当于上述情况下增加了他们权值和的花费,而右边的物品减少了权值和的花费,如果左边的权值和较大,那说明当前情况不如上述情况最优的,如果右边权值和较大,我们可以再把右边的一段往右移,左边的一段往右移,这样的话有减小了花费,知道右边的一段有一个回到了原来的位置,这时候上述情况中不动的物品就进行了转移,但依旧是当前最优的;

------其实上面这个东西是带权中位数

 

至于这个不动的物品怎么找,通过上面反证的过程也可以知道,上面是假设左边一段往右移动,右边一段往左移动,如果左边的权值和小于右边,那就继续往右移动,直到右边有一个物品回到了原来的位置,在进行移动的时候那这个物品就归到了左边的一段,如果此时左边的一段权值和大于等于右边,那就不用移动了,所以我们把要合并的一段从左边往右找,第一个使得左边一段权值和大于右边的位置的物品就是不需要动的物品,这个过程可以用树状数组维护前缀和后二分查找(因为涉及到修改)

 

至于找到这个位置以后我们也不能每个位置的物品挨个算花费,所以还要维护一下,如果要是合并到一个位置上那就比较简单了,直接用树状数组维护每个物品的 权值和*位置,算结果的时候 直接用 权值和*不动物品的位置减去就好,这个过程有树桩数组的前缀和可以快速解决;虽然每个物品到移动后的位置不确定,但是他们的位置关系是确定的,我们可以给每个位置加上不同的数让他们的差值变成相减后移动的位置差,也就是因为两个位置之间有多少个物品,那他们之间的差就会小多少;

 

 

 

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const ll mod = 1e9 + 7;


int n, q;
ll id[maxn], w[maxn];


// 权值w[i]前缀和  用于寻找带权中位数
ll c[maxn];
void add(int id, ll v) {
    for(int i = id; i < maxn; i += (i&-i)) {
        c[i] += v;
    }
}
ll sum(int id) {
    ll res = 0;
    for(int i = id; i > 0; i -= (i&-i)) {
        res += c[i];
    }
    return res;
}

// 维护 id[i]*w[i];  最后求答案
ll c1[maxn];
void add1(int id, ll v) {
    for(int i = id; i < maxn; i += (i&-i)) {
        (c1[i] += v) %= mod;
    }
}
ll sum1(int id) {
    ll res = 0;
    for(int i = id; i > 0; i -= (i&-i)) {
        res += c1[i];
    }
    return res%mod;
}

int main() {
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld", &id[i]);
        id[i] += (n - i);
    }
    for(int i = 1; i <= n; ++i) {
        scanf("%lld", &w[i]);
        add(i, w[i]);
        add1(i, id[i]*w[i]%mod);
    }
    while(q--) {
        ll l_, r_;
        scanf("%lld", &l_);

        if(l_ < 0) {
            l_ = -l_;
            scanf("%lld", &r_);
            add(l_, r_-w[l_]);
            add1(l_, (id[l_]*(r_-w[l_])%mod));
            w[l_] = r_;
        } else {
            scanf("%lld", &r_);
            ll u = sum(l_-1);
            ll t = (sum(r_) - u);

            int l = l_, r = r_;
            int pos = -1;
            while(l <= r) {
                int mid = (l + r) >> 1;
                ll num = (sum(mid) - u);
                if(num*2 >= t) {
                    pos = mid;
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
            ll ans = ( (sum(pos)-sum(l_-1))%mod*id[pos]%mod - ((sum1(pos))-(sum1(l_-1)+mod))%mod + mod) % mod; // 左边的ans
            ans = (ans + ((sum1(r_))-(sum1(pos-1))+mod)%mod - (sum(r_)-sum(pos-1))%mod*id[pos] + mod  ) % mod; // 右边的ans
            ans = (ans % mod + mod) % mod;
            printf("%lld\n", ans);
        }
    }

    return 0;
}








 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值