[SMOJ2166]数列的和

97 篇文章 0 订阅
5 篇文章 0 订阅

10%:
朴素做法,对于每一个 i 枚举考察前面的每一个 j,直接计算。
时间复杂度: O(n2)

50%:
可以将绝对值符号变成分类讨论,即

Bi=(AiAj) Aj<Ai+(AjAi) Aj>Ai

显然,如果设 A1 Ai1 中有 k1 个数小于 Ai ,有 k2 个数大于 Ai ,则对于 Aj<Ai,Bi+=AiAj ,大于 Ai 的情况同理。不难发现可以推出

Bi=(k1×AiAj Aj<Ai)(k2×AiAj Aj>Ai)

因此,不妨记 s1=Aj Aj<Ai,s2=Aj Aj>Ai ,显然有

Bi=(k1Ais1)(k2Ais2)=(k1k2)Ai+(s2s1)

其中 Ai 为已知值,只需维护 k1,k2,s1 s2 即可算出 Bi 。问题转化为求所有满足 j<i Aj<Ai j 的个数 k1 及满足条件的 Aj 之和 s1 ,大于同理。
考虑到 50% 的数据 Ai200000 ,数字较小,可以用两个 BIT,一个维护数字出现的个数以计算 k ,另一个维护具体的 Ai 值以计算 s 。可合并为一棵线段树。
时间复杂度:O(nlog2Ai)

100%:
做法一:离散化+50% 的做法
注意到 N200000 不会很大,直接根据 Ai 进行维护会导致很大的浪费,此时就可以对数列进行离散化之后,再用 50% 的做法。
时间复杂度: O(nlog2n) ,但是常数相对做法二更大,因为预先的排序需要耗费跟求解过程一样多的时间。

做法二:Treap
利用 Treap 的 BST 性质,只需维护 cnt size (见模板题的笔记)和 sum 域,即可快速求得两个 k s。具体说明一下。
我们用一个结点的 sum 值表示以该结点为根的子树所有结点的值之和(包含重复出现的),目标是用 Treap 求出比给定的 Ai 小的个数和它们的总和。
到达一个结点时,可以分为三种情况讨论:

  • 当前结点为空,则返回 0 个数小于 Ai ,和为 0
  • Ai 小于等于当前结点,则答案肯定在左子树中,递归求解左子树并返回其答案
  • Ai 大于当前结点,则左子树肯定也都小于 Ai ,因此至少有 sizel+cntu 个数小于 Ai ,它们的和为 suml+cntu×valu ,再与右子树答案相加

求大于部分的过程类似,只是把判断反了过来。

注意,大于和小于应该分开求解。我一开始是大于和小于同时求,再根据需要返回,这样在搜索树中,每个结点都会扩展出两棵子树,复杂度就变成了 O(2n)
事实上,对于一个确定的结点,它要么小于等于 Ai ,要么大于,而可以根据情况递归求解唯一不确定的子树(一定有其中一棵子树的答案是可以确定的。想一想,为什么。)这样递归次数只与 Treap 的深度有关,增长速度比较慢。或者也可以考虑把求解小于和大于的过程分开实现,可能会更清晰一些。
时间复杂度: O(nlog2n)

参考代码:

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

using namespace std;

typedef pair <int, long long> pil;

const int MAXN = 2e5 + 100;
const int MOD = 1000000007LL;

/*
struct QueryResult {
    int less_cnt, greater_cnt;
    long long less_sum, greater_sum;
};
*/

struct Tnode {
    Tnode *child[2];
    long long val, sum; int fix;
    int cnt, siz;
    Tnode (long long v = 0LL) : val(v), sum(v), fix(rand()), cnt(1), siz(1) {
        child[0] = child[1] = 0;
    }

    void update() {
        int lsiz = (child[0] ? child[0] -> siz : 0);
        int rsiz = (child[1] ? child[1] -> siz : 0);
        siz =  cnt + lsiz + rsiz;

        int lsum = (child[0] ? child[0] -> sum : 0);
        int rsum = (child[1] ? child[1] -> sum : 0);
        sum = (lsum + rsum + cnt * val % MOD) % MOD;
    }
};

struct Treap {
    Tnode *root;
    Treap () : root(0) {}

    void rotate(Tnode *&cur, int dir) {
        Tnode *ch = cur -> child[dir ^ 1];
        cur -> child[dir ^ 1] = ch -> child[dir];
        ch -> child[dir] = cur;

        cur -> update();
        ch -> update();
        cur = ch;
    }

    void insert_val(Tnode *&cur, int v) {
        if (!cur) cur = new Tnode(v);
        else {
            if (v == cur -> val) ++(cur -> cnt);
            else {
                int t = v > cur -> val;
                insert_val(cur -> child[t], v);
                if (cur -> child[t] -> fix < cur -> fix) rotate(cur, t ^ 1);
            }
            cur -> update();
        }
    }

    /*
    QueryResult query(Tnode *cur, int v) {
        if (!cur) return (QueryResult){0, 0, 0LL, 0LL};
        QueryResult lres = query(cur -> child[0], v), rres = query(cur -> child[1], v);

        int lsiz = (cur -> child[0] ? cur -> child[0] -> siz : 0);
        int rsiz = (cur -> child[1] ? cur -> child[1] -> siz : 0);
        int lsum = (cur -> child[0] ? cur -> child[0] -> sum : 0);
        int rsum = (cur -> child[1] ? cur -> child[1] -> sum : 0);

        if (cur -> val == v) return (QueryResult){lres.less_cnt, rres.greater_cnt, lres.less_sum, rres.greater_sum};
        else if (v < cur -> val) return (QueryResult){lres.less_cnt, lres.greater_cnt + cur -> cnt + rsiz, lres.less_sum, ((lres.greater_sum + cur -> val * cur -> cnt % MOD) % MOD + rsum) % MOD};
        else return (QueryResult){lsiz + cur -> cnt + rres.less_cnt, rres.greater_cnt, ((lsum + cur -> val * cur -> cnt % MOD) % MOD + rres.less_sum) % MOD, rres.greater_sum};
    }
    */

    pil query_min(Tnode *cur, int v) { //在以 cur 为根的子树中找比 v 小的数,返回值的 first 为个数,second 为总和
        if (!cur) return make_pair(0, 0); //空结点

        if (v <= cur -> val) return query_min(cur -> child[0], v); //比当前结点值还要小,只有在左子树中才可能有比 v 小的值
        else {
            pil t = query_min(cur -> child[1], v); //比当前结点大,左子树和当前结点都比 v 小,且右子树中可能还存在比 v 小的值
            int lsiz = (cur -> child[0] ? cur -> child[0] -> siz : 0);
            long long lsum = (cur -> child[0] ? cur -> child[0] -> sum : 0LL);
            return make_pair(lsiz + cur -> cnt + t.first, ((lsum + cur -> val * cur -> cnt % MOD) % MOD + t.second) % MOD); //合并答案
        }
    }

    pil query_max(Tnode *cur, int v) { //同理,只是都反了过来
        if (!cur) return make_pair(0, 0LL);

        if (v >= cur -> val) return query_max(cur -> child[1], v);
        else {
            pil t = query_max(cur -> child[0], v);
            int rsiz = (cur -> child[1] ? cur -> child[1] -> siz : 0);
            long long rsum = (cur -> child[1] ? cur -> child[1] -> sum : 0LL);
            return make_pair(t.first + cur -> cnt + rsiz, ((t.second + cur -> val * cur -> cnt % MOD) % MOD + rsum) % MOD);
        }
    }

    void debug_output(Tnode *&cur) { //调试输出,中序遍历以 cur 为根的 Treap
        putchar('(');
        if (cur -> child[0]) debug_output(cur -> child[0]);
        putchar(')');
        printf("%lld ", cur -> val);
        putchar('(');
        if (cur -> child[1]) debug_output(cur -> child[1]);
        putchar(')');
    }
} lkb_treap;

int N;
long long A[MAXN];

int main(void) {
    freopen("2166.in", "r", stdin);
    freopen("2166.out", "w", stdout);
    scanf("%d", &N);
    for (int i = 0; i < N; i++) scanf("%lld", &A[i]);

    long long ans = 1, sum = A[0]; lkb_treap.insert_val(lkb_treap.root, A[0]); //将第一个数作为根
    for (int i = 1; i < N; i++) {
//      QueryResult ret = lkb_treap.query(lkb_treap.root, A[i]);
        pil mn = lkb_treap.query_min(lkb_treap.root, A[i]), mx = lkb_treap.query_max(lkb_treap.root, A[i]);
        long long B = ((mn.first * A[i] % MOD - mn.second + MOD) % MOD + (mx.second - mx.first * A[i] % MOD + MOD) % MOD) % MOD;
//      printf("%lld\n", B);
        (ans *= B) %= MOD; //虽然本题中不会影响答案,但先计算再更新比先更新再计算更稳妥
        lkb_treap.insert_val(lkb_treap.root, A[i]);
//      lkb_treap.debug_output(lkb_treap.root); putchar('\n');
    }
    printf("%lld\n", ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值