51nod - 1394 差和问题

有一个多重集合S(即里面元素可以有重复),初始状态下有n个元素,对他进行如下操作:

1、向S里面添加一个值为v的元素。输入格式为1v

2、向S里面删除一个值为v的元素。输入格式为2v

3、询问S里面的元素两两之差绝对值之和。输入格式为3

 

对于样例,

操作3,|1-2|+|1-3|+|2-3|=4

操作1 4之后,集合中的数字为1 2 3 4

操作3,|1-2|+|1-3|+|2-3|+|1-4|+|2-4|+|3-4|=10

操作2 2之后,集合中的数字为1 3 4

操作3,|1-3|+|1-4|+|3-4|=6

 

Input

第一行输入两个整数n,Q表示集合中初始元素个数和操作次数。(1<=n,Q<=100,000)
第二行给出n个整数a[0],a[1],a[2],…,a[n-1],表示初始集合中的元素。(0<=a[i]<=1,000,000,000) 
接下来Q行,每行一个操作。(0<=v<=1,000,000,000)

Output

对于第2类操作,如果集合中不存在值为v的元素可供删除,输出-1。
对于第3类操作,输出答案。

Input示例

3 5
1 2 3
3
1 4
3
2 2
3

Output示例

4
10
6

思路:

对于递增的n个数a[1],a[2]...a[n],如果已经计算出1至n-1的差和,这时a[n]贡献的差和为(a[n] - a[1]) + (a[n] - a[2]) + ... + (a[n] - a[n-1]) = (n-1) * a[n] - (a[1] + a[2] + ... + a[n-1])。因此这又是一个求前缀和的问题,用树状数组来解决。

在本题中,增加一个数t[i]后的结果变更为:result = result + (t[i] * (ansn * 2 - sumn) - 2 * anss + sum);

(t[i] * (ansn * 2 - sumn) - 2 * anss + sum)是考虑导t[i]对它之前的差和贡献和对它之后的差和贡献。

将所有出现的数(包括后来增加的数)排序。分别用两个数组统计增减一个数后,该数之前的和以及该数之前的数量。

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

typedef long long ll;
const int MAXN = 2e5 + 10;  
struct node  
{  
    int id;  
    ll v;  
}q[MAXN];  
ll a[MAXN*4];  
int b[MAXN*4];  
ll t[MAXN];  
ll cur[MAXN];  
int num[MAXN];  

ll low(int k)  
{  
    return k & (-k);  
}

void update(int k, ll v, int v2)  
{  
    while (k < MAXN)  
    {  
        a[k] += v;  
        b[k] += v2;  
        k += low(k);  
    }  
}  
ll anss, ansn;  

ll sums(int k)  
{  
    anss = 0;
	ansn = 0;  
    while (k > 0)  
    {  
        anss += a[k];  
        ansn += b[k];  
        k -= low(k);  
    }  
}  

int main()  
{  
    int n, Q;
	cin >> n >> Q;
    for (int i = 0; i < n; i++)  
    {  
		cin >> t[i];
        cur[i] = t[i];  
    }  
    int cnt = n;  
    for (int i = 0; i < Q; i++)  
    {  
		cin >> q[i].id;
        if (q[i].id != 3)  
        {  
			cin >> q[i].v;
            cur[cnt] = q[i].v;  
            cnt++;  
        }  
    }  
    sort(cur, cur + cnt);  
    int all = unique(cur, cur + cnt) - cur;  
    ll sum = 0;
	ll result = 0;  
    int sumn = 0;  
    int temp;  
    for (int i = 0; i < n; i++)  
    {  
        temp = lower_bound(cur, cur + all, t[i]) - cur + 1; 
        num[temp]++;  
        sumn++;  
        update(temp, t[i], 1);  
        sum += t[i];  
        sums(temp);  
        result = result + (t[i] * (ansn * 2 - sumn) - 2 * anss + sum); 
    }  
    ll cwt;  
    for (int i = 0; i < Q; i++)  
    {  
        if (q[i].id == 1)  
        {  
            temp = lower_bound(cur,cur+all, q[i].v) - cur + 1;  
            update(temp, q[i].v, 1);  
            num[temp]++;  
            sum += q[i].v;  
            sumn++;  
            sums(temp);  
            result = result + (q[i].v * (ansn * 2 - sumn) - 2 * anss + sum);  
        }  
        else if (q[i].id == 2)  
        {  
            temp = lower_bound(cur, cur+all, q[i].v) - cur + 1;  
            if (num[temp] == 0)  
            {  
				cout << "-1" << endl;
            }  
            else  
            {  
                sums(temp);  
                result = result - (q[i].v * (ansn * 2 - sumn) - 2 * anss + sum);  
                update(temp, -q[i].v, -1);  
                num[temp]--;  
                sum -= q[i].v;  
                sumn--;  
            }  
        }  
        else if (q[i].id == 3)  
        {  
			cout << result << endl;
        }  
    }
	
    return 0;  
}  

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值