2021“MINIEYE杯”中国大学生算法设计超级联赛(5)

1009 Array(树状数组)

描述
给定序列,找存在绝对众数的区间 (区间的众数出现次数大于区间长度一半) 个数。

分析
本题与洛谷上一题除了数据范围是一模一样的。
题解
这题同样用线段树求理论上是可以过的,但是线段树常数可能大一下,以至于会被卡TLE 但是实验室有人线段树过了
考虑用常数更小一点的树状数组。
要求是要维护-1,1序列的sum数组,做到区间修改,区间查询二阶前缀和。
然后在树状数组中要想做到区间修改,只能对差分数组进行单点修改,这样原序列就变成了差分数组的一阶前缀和,要实现的区间查询二阶前缀和变成了三阶前缀和。
设 a,b,c,d分别为原数组(差分数组),一阶前缀和数组,二阶前缀和数组,三阶前缀和数组,那么有:

d [ x ] = ∑ i = 1 x ∑ j = 1 i ∑ k = 1 j a [ k ] d[x]=\sum_{i=1}^x\sum_{j=1}^i\sum_{k=1}^ja[k] d[x]=i=1xj=1ik=1ja[k]

然后分析一下这个式子:

∑ j = 1 i ∑ k = 1 j a [ k ] \sum_{j=1}^i\sum_{k=1}^ja[k] j=1ik=1ja[k]

= a 1 + =a_1 + =a1+
     a 1 + a 2 + \ \ \ \ a_1+a_2+     a1+a2+
     a 1 + a 2 + a 3 + \ \ \ \ a_1+a_2+a_3+     a1+a2+a3+
                 . . .        + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ...\ \ \ \ \ \ +                 ...      +
     a 1 + a 2 + a 3 + . . . + a i \ \ \ \ a_1+a_2+a_3+...+a_i     a1+a2+a3+...+ai

= i a 1 + ( i − 1 ) a 2 + . . . + a i =ia_1+(i-1)a_2+...+a_i =ia1+(i1)a2+...+ai

所以就有

d [ x ] = d[x]= d[x]=

a 1 + a_1+ a1+
2 a 1 + a 2 + 2a_1+a_2+ 2a1+a2+
            . . .           + \ \ \ \ \ \ \ \ \ \ \ ...\ \ \ \ \ \ \ \ \ +            ...         +
i a 1 + ( i − 1 ) a 2 + . . . + a i ia_1+(i-1)a_2+...+a_i ia1+(i1)a2+...+ai
            . . .                               + \ \ \ \ \ \ \ \ \ \ \ ...\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ +            ...                             +
x a 1 + ( x − 1 ) a 2 + . . . + 2 a x − 1 + a x xa_1+(x-1)a_2+...+2a_{x-1}+a_x xa1+(x1)a2+...+2ax1+ax

可以发现最终 a 1 , a 2 , . . . , a x a_1,a_2,...,a_x a1,a2,...,ax的系数式等差数列的前缀和,运用等差数列求和公式,有:

d [ x ] = ∑ i = 1 x ( x − i + 2 ) ( x − i + 1 ) 2 a i d[x]=\sum_{i=1}^x\frac{(x-i+2)(x-i+1)}{2}a_i d[x]=i=1x2(xi+2)(xi+1)ai

         = ( x + 1 ) ( x + 2 ) 2 ∑ i = 1 x a i − 2 x + 3 2 ∑ i = 1 x i a i + 1 2 ∑ i = 1 x i 2 a i \ \ \ \ \ \ \ \ =\frac{(x+1)(x+2)}{2}\sum_{i=1}^{x}a_i-\frac{2x+3}{2}\sum_{i=1}^{x}ia_i+\frac{1}{2}\sum_{i=1}^{x}i^2a_i         =2(x+1)(x+2)i=1xai22x+3i=1xiai+21i=1xi2ai

最后像线段树做法一样加上一个偏移量,做区间修改区间查询就可以了。


代码

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pli pair<ll,int>
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))

typedef long long ll;
typedef unsigned long long ull;

const double pi = 3.141592653589793;
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;

const int N = 1000010;

int n, del, a[N];
vector<int> vec[N];
ll c1[N << 1], c2[N << 1], c3[N << 1];

ll ask(int x)
{
    ll ans = 0;
    for (int i = x; i; i -= i & -i)
        ans += (ll)(x + 1) * (x + 2) * c1[i] - (ll)(2 * x + 3) * c2[i] + c3[i];
    return ans / 2;
}

void change(int x, int d)
{
    for (int i = x; i <= n + del; i += i & -i)
    {
        c1[i] += d;
        c2[i] += (ll)x * d;
        c3[i] += (ll)x * x * d;
    }
}

int main()
{   
    
    int t;
    scanf("%d", &t);    
    while (t--)
    {
        scanf("%d", &n);
        int mx = 0, mn = INF;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            mx = max(mx, a[i]);
            mn = min(mn, a[i]);
            vec[a[i]].push_back(i);
        }
    
        del = n + 2;
        ll ans = 0;
    
        for (int i = mn; i <= mx; i++)
        {
            if (vec[i].empty()) continue;
            vec[i].push_back(n + 1);

            int pre = 0, sum = 0;
            for (int j = 0; j < vec[i].size(); j++)
            {
                int now = vec[i][j] - 1;
                int low = sum - (now - pre), up = sum;
                ans += ask(up - 1 + del) - ask(low - 2 + del);
                change(low + del, 1);
                change(up + 1 + del, -1);
                pre = now + 1;
                sum = low + 1;
            }
            
            pre = 0, sum = 0;
            for (int j = 0; j < vec[i].size(); j++)
            {
                int now = vec[i][j] - 1;
                int low = sum - (now - pre), up = sum;
                change(low + del, -1);
                change(up + 1 + del, 1);
                pre = now + 1;
                sum = low + 1;
            }
           vec[i].clear();   
        }

        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值