1 条题解

  • 0
    @ 2025-8-24 22:31:36

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 南阳刘子骥
    Alles Sie mögen ist keine Last.

    搬运于2025-08-24 22:31:36,当前版本为作者最后更新于2022-05-23 21:54:39,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    我们首先考虑没有修改的情况。

    首先,我们假设已经给所有的居民分配好了一个烘焙披萨的方案,并用 posipos_i 代表第 ii 位居民的披萨是第 posipos_i 个烘焙的。

    这样的话,我们设 CiC_i 为从第 ii 名居民身上获取的小费数额(如果为负数则表示需要向该位居民支付的数额的相反数),那么就会有如下的式子:

    Ci=Lij=1posiTjC_i = L_i - \sum_{j=1}^{pos_i} T_j

    那么对于 Mirko 收获小费的总和 i=1nCi\sum_{i=1}^n C_i,我们可以推一下式子:

    $$\begin{aligned} \sum_{i=1}^n C_i &= \sum_{i=1}^n (L_i - \sum_{j=1}^{pos_i} T_j) \\ &= \sum_{i=1}^n L_i - \sum_{i=1}^n \sum_{j=1}^{pos_i} T_j \\ &= \sum_{i=1}^n L_i - \sum_{i=1}^n T_i \times (n- pos_i + 1) \end{aligned} $$

    最后一步是这样推出来的:

    对于 TiT_i 来说,它会在计算所有 posjposipos_j \geq pos_ijj 的时候被计算到,所以总共就是 j=posinTi\sum_{j=pos_i}^n T_i,即 Ti×(nposi+1)T_i \times (n-pos_i + 1)


    然后看带修改的。

    我们看刚才推出来的式子,可以看出我们能够对于 LLTT 分开考虑,而事实上我们也就是这样做的。

    我们对于一开始没有被修改时的数据计算出一个 suml=i=1nLisuml = \sum_{i=1}^n L_i,然后再计算出一个 sumt=i=1nTi×(nposi+1)sumt = \sum_{i=1}^n T_i \times (n- pos_i + 1)
    我们最终输出的数值就是 sumlsumtsuml-sumt

    假设我们有了一个新的修改,将 (Lx,Tx)(L_x,T_x) 修改为 (Ly,Ty)(L_y,T_y)

    对于 sumlsuml,其只需要变为 sumlLx+Lysuml-L_x+L_y 即可。

    对于 sumtsumt,我们可以看成首先从数列里面删除了一个 TxT_x,然后再插入了一个 TyT_y;其中 TxT_x 删除前的位置是 posxpos_xTyT_y 删除后的位置是 posypos_y

    我们可以将删除和插入分开讨论,也可以只讨论改变位置的元素。

    如果分开讨论删除和插入的话,我们的分析过程是这个样子的:

    1. 对于 Ti<Tx\forall T_i < T_x,我们的 posipos_i 不会变,但是 nn 会因删除而减小 11;而对于 TiTx\forall T_i \geq T_x,我们的 posipos_inn 都会减小 11 而最终抵消;对于 TxT_x,我们需要减去它的贡献。

    所以我们的 sumtsumt 在删除 TxT_x 之后会变成这个样子:

    $$sumt \to sumt - T_x \times (n - pos_x + 1) - \sum_{T_i<T_x} T_i $$
    1. 对于 Ti<Tx\forall T_i < T_x,我们的 posipos_i 不会变,但是 nn 会因插入而增大 11;而对于 TiTx\forall T_i \geq T_x,我们的 posipos_inn 都会增大 11 而最终抵消;对于 TyT_y,我们需要加上它的贡献。

    所以我们的 sumtsumt 在插入 TyT_y 之后会变成这个样子:

    $$sumt \to sumt + T_x \times (n - pos_y + 1) + \sum_{T_i<T_x} T_i $$

    如果只讨论改变位置的元素的话,我们的分析过程是这个样子的:

    1. 如果 Tx<TyT_x < T_y,那么对于 {Tiposi(posx,posy)}\{T_i | pos_i \in (pos_x,pos_y)\},其 posipos_i 会减少 11,从而导致 sumtsumt 减少 posi(posx,posy)Ti\sum_{pos_i \in (pos_x,pos_y)} T_i

    2. 如果 Tx>TyT_x > T_y,那么对于 {Tiposi(posy,posx)}\{T_i | pos_i \in (pos_y,pos_x)\},其 posipos_i 会增加 11,从而导致 sumtsumt 增加 posi(posx,posy)Ti\sum_{pos_i \in (pos_x,pos_y)} T_i

    总的来看,我们的变化量可以看做 Ti<TxTiTi<TxTi\sum_{T_i<T_x} T_i - \sum_{T_i<T_x} T_i

    再加上 TyT_y 的贡献,减去TxT_x 的贡献,我们推出的式子跟上面的是一样的。


    于是我们就需要一种数据结构,支持

    1. 插入和删除元素
    2. 查询小于一个元素的数字个数
    3. 查询小于一个元素的数字之和

    树状数组、权值线段树和平衡树均可。

    我这里使用的是替罪羊树。

    对于不知道替罪羊树的人,我在这里安利一下我的博客,同时也给出OI-Wiki关于替罪羊树的讲解。

    替罪羊树虽然比较慢,但是所有的操作时间复杂度均摊之后都是 O(logn)O(\log n ) 级别的。

    这里粘一下代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N = 200010;
    ll n, m;
    double alpha = 0.75;
    struct Scapegoat
    {
    	int ls, rs;
    	ll w, wn;
    	int s, sz, sd;
    	ll sum;
    }tr[N];
    int cnt, rt;
    void calc(int p)
    {
    	tr[p].s = tr[tr[p].ls].s + tr[tr[p].rs].s + 1;
    	tr[p].sz = tr[tr[p].ls].sz + tr[tr[p].rs].sz + tr[p].wn;
    	tr[p].sd = tr[tr[p].ls].sd + tr[tr[p].rs].sd + (tr[p].wn != 0);
    	tr[p].sum = tr[tr[p].ls].sum + tr[tr[p].rs].sum + tr[p].wn * tr[p].w;
    }
    bool canrbu(int p)
    {
    	return tr[p].wn && (alpha * tr[p].s <= ( double )max(tr[tr[p].ls].s, tr[tr[p].rs].s) || ( double )tr[p].sd <= alpha * tr[p].s);
    }//can rebuild
    int ldr[N];
    void rbuunf(int &ldc, int p)
    {
    	if(!p)return;
    	rbuunf(ldc, tr[p].ls);
    	if(tr[p].wn)ldr[ldc++] = p;
    	rbuunf(ldc, tr[p].rs);
    }//rebuild-unfold
    int rbubld(int l, int r)
    {
    	if(l >= r)return 0;
    	int mid = (l + r) >> 1;
    	tr[ldr[mid]].ls = rbubld(l, mid);
    	tr[ldr[mid]].rs = rbubld(mid + 1, r);
    	calc(ldr[mid]);
    	return ldr[mid];
    }//rebuild-build
    void rbuild(int &p)
    {
    	int ldc = 0;
    	rbuunf(ldc, p);
    	p = rbubld(0, ldc);
    }//rebuild
    void insert(int &p, ll k)
    {
    	if(!p)
    	{
    		p = ++cnt;
    		if(!rt)rt = 1;
    		tr[p].w = k;
    		tr[p].ls = tr[p].rs = 0;
    		tr[p].wn = tr[p].s = tr[p].sz = tr[p].sd = 1;
    		tr[p].sum = k;
    	}
    	else
    	{
    		if(tr[p].w == k)tr[p].wn++;
    		else if(tr[p].w < k)insert(tr[p].rs, k);
    		else insert(tr[p].ls, k);
    		calc(p);
    		if(canrbu(p))rbuild(p);
    	}
    }
    void loschn(int &p, ll k)
    {
    	if(!p)return;
    	if(tr[p].w == k)
    	{
    		if(tr[p].wn)tr[p].wn--;
    	}
    	else
    	{
    		if(tr[p].w < k)loschn(tr[p].rs, k);
    		else loschn(tr[p].ls, k);
    	}
    	calc(p);
    	if(canrbu(p))rbuild(p);
    }//löschen,delete是关键字就不用了
    ll uprgtr(int p, ll k)
    {
    	if(!p)
    		return 0;
    	else if(tr[p].w == k && tr[p].wn)
    		return tr[tr[p].ls].sz;
    	else if(tr[p].w < k)
    		return tr[tr[p].ls].sz + tr[p].wn + uprgtr(tr[p].rs, k);
    	else
    		return uprgtr(tr[p].ls, k);
    }//相当于是使用greater<>函数排序之后的upper_bound
    //输出的结果是小于某个元素的数的个数
    ll uprsum(int p, ll k)
    {
    	if(!p)
    		return 0;
    	else if(tr[p].w == k && tr[p].wn)
    		return tr[tr[p].ls].sum;
    	else if(tr[p].w < k)
    		return tr[tr[p].ls].sum + tr[p].wn * tr[p].w + uprsum(tr[p].rs, k);
    	else
    		return uprsum(tr[p].ls, k);
    }//跟上面差不多,输出的是小于某个元素的数之和
    ll suml, sumt;
    ll l[N], t[N];
    int main()
    {
    	scanf("%lld%lld", &n, &m);
    	int temp[N];
    	for(int i = 1; i <= n; i++)
    	{
    		scanf("%lld%lld", &l[i], &t[i]);
    		suml += l[i];
    		insert(rt, t[i]);
    		temp[i] = t[i];
    	}
    	sort(temp + 1, temp + 1 + n);
    	for(int i = 1; i <= n; i++)
    		sumt += (n - i + 1) * temp[i];
    	printf("%lld\n", suml - sumt);
    	while(m--)
    	{
    		int a;
    		ll b, c;
    		scanf("%d%lld%lld", &a, &b, &c);
    		suml -= l[a] - b;
    		l[a] = b;
    		ll sum1 = uprsum(rt, t[a]), cnt1 = uprgtr(rt, t[a]);
    		loschn(rt, t[a]);
    		insert(rt, c);
    		ll sum2 = uprsum(rt, c), cnt2 = uprgtr(rt, c);
    		sumt += c * (n - cnt2) + sum2 - t[a] * (n - cnt1) - sum1;
    		t[a] = c;
    		printf("%lld\n", suml - sumt);
    	}
        return 0;
    }
    

    感谢阅读~qwq

    • 1

    信息

    ID
    6752
    时间
    1000ms
    内存
    128MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者