51Nod-1712-区间求和

235 篇文章 1 订阅
182 篇文章 3 订阅

ACM模版

描述

描述

题解

做这道题明显感觉智商不够用,只能说略懂略懂……套路太深,数论有些差了/(ㄒoㄒ)/~~不是看了大牛们的详解我断然无法弄懂这道题。

以下是官方题解:
对于一段区间l~r,其中一个数x对答案的贡献为(2x-l-r)次。
因此我们只要求出所有数对答案的贡献并累加起来即可。
将2x-l-r分为两部分,一部分为求2x的和,即为x左边与x右边相同的数的对数。
另一部分为l+r,将其拆开来,并记录前缀和,对于一个数a[i],我们需要维护的是Σk,Σs[i-1],Σs[i-1]*i,以及a[i]出现的次数就可以了。
这里我们可以在枚举的同时,记录这些信息,并更新答案,就可以了。
复杂度为线性O(n)。

如果感觉这个不够详细,可以去看看zkGaia的blog,十分详细,虽然有的地方并不能悟透,建议这两个结合起来理解,因为我感觉这个博客里的题解讲得有些地方不是承上启下得太好,有点懵逼了我(@ο@) 当然,也要结合代码去好好理解,哎,我太笨了,是硬伤啊~~~

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int MAXN = 3e6 + 10;

typedef unsigned int uint;

int n, lrk[MAXN], rrk[MAXN], pre[MAXN], nxt[MAXN], pos[MAXN];
uint a[MAXN], decA[MAXN], decB[MAXN], f[MAXN], g[MAXN], ans = 0;

void in(uint &w)
{
    w = 0;
    char c = getchar();
    while (c > '9' || c < '0')
    {
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        w = w * 10 + c - '0';
        c = getchar();
    }
}

uint mult(uint a, uint b)
{
    uint s = 0;
    while (b)
    {
        if (b & 1)
        {
            s += a;
        }
        a += a;
        b >>= 1;
    }
    return s;
}

int main()
{
    scanf("%d", &n);

    for (int i = 1; i <= n; i++)
    {
        in(a[i]);
    }
    memset(pos, 0, sizeof(pos));
    for (int i = 1; i <= n; i++)
    {
        pre[i] = pos[a[i]];
        pos[a[i]] = i;
        lrk[i] = lrk[pre[i]] + 1;
        decA[i] = decA[pre[i]] + pre[i];
    }
    memset(pos, 0, sizeof(pos));
    for (int i = n; i >= 1; i--)
    {
        nxt[i] = pos[a[i]];
        pos[a[i]] = i;
        rrk[i] = rrk[nxt[i]] + 1;
        decB[i] = decB[nxt[i]] + nxt[i];
    }
    for (int i = 1; i <= n; i++)
    {
        if (rrk[i] == 1)
        {
            nxt[i] = i + n;
            pre[i + n] = i;
            lrk[nxt[i]] = lrk[i] + 1;
            decA[i + n] = decA[i] + i;
        }
        if (lrk[i] == 1)
        {
            pre[i] = i + n * 2;
            nxt[i + n * 2] = i;
            rrk[pre[i]] = rrk[i] + 1;
            decB[i + n * 2] = decB[i] + i;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        f[i] = f[i - 1] + i * rrk[i] - decA[nxt[i - 1]];
    }
    for (int i = n; i >= 1; i--)
    {
        g[i] = g[i + 1] + i * lrk[i] - decB[pre[i + 1]];
    }
    for (int i = 1; i <= n; i++)
    {
        f[i] = f[i] + g[i] - i * 2;
    }
    memset(g, 0, sizeof(g));
    memset(decA, 0, sizeof(decA));
    for (int i = 1; i <= n; i++)
    {
        decA[i] = decA[pre[i]] + (pre[i] >= 1 && pre[i] <= n);
        if (rrk[i] == 1)
        {
            decA[i + n] = decA[i] + 1;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        g[i] = g[i - 1] + rrk[i] - decA[nxt[i - 1]];
        f[i] = mult(g[i] - 1, i) * 2 - f[i];
        ans += mult(f[i], a[i]); 
    }
    cout << ans << endl;

    return 0; 
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值