CSP认证——序列查询新解

题意解读:

有一个长度为 n n n的序列 A A A,序列 A A A中的所有元素的取值在 [ 0 , N − 1 ] [0,N-1] [0,N1]中, f ( x ) f(x) f(x)代表序列 A A A小于等于 x x x最大整数下标(注意看清题目),定义 g ( x ) g(x) g(x) x / r x/r x/r(下取整),其中 r = N / ( n + 1 ) r=N/(n+1) r=N/(n+1)(下取整),求 ∑ i = 0 N − 1 ∣ f ( i ) − g ( i ) ∣ \sum_{i=0}^{N-1}{|f(i)-g(i)|} i=0N1f(i)g(i),也就是估计值与真实值的误差和。

分析:

  • 此题的 N N N给到了 1 e 9 1e9 1e9的范围,所以我们遍历 N N N的话一定会超时,所以要考虑其他办法,此处我的做法是枚举n。

  • 分析这个误差和,我们可以看到它带着绝对值,如果我们把绝对值拆开,那么我们就可以把 ∑ i = 0 N − 1 ∣ f ( i ) − g ( i ) ∣ \sum_{i=0}^{N-1}{|f(i)-g(i)|} i=0N1f(i)g(i)转化成如 ∑ i = 0 N − 1 f ( i ) − ∑ i = 0 N − 1 g ( i ) \sum_{i=0}^{N-1}{f(i)}-\sum_{i=0}^{N-1}{g(i)} i=0N1f(i)i=0N1g(i)类似的形式,这样做的好处就在于我们可以使用 O ( 1 ) O(1) O(1)的时间复杂度求出连加。

  • 我们知道 f ( x ) f(x) f(x)代表序列 A A A小于等于 x x x最大整数下标,那么如果我们遍历序列 A A A,对于 A [ i ] A[i] A[i]我们可以推出,以 i i i f f f值的 x x x的范围就是在 [ A ( i ) , A ( i + 1 ) − 1 ] [A(i),A(i+1)-1] [A(i),A(i+1)1]之间,那么这样我们就可以很轻松的求出有关 f f f的区间和,通过一行代码:

    ll f = i * (right - left + 1)
    
  • 找到了求 f f f区间和的简便方法之后,我们就开始讨论求 g g g的区间和的方法。我们知道 g ( x ) g(x) g(x)= x / r x/r x/r(下取整),下取整赋予了它一个非常重要的性质,当 x < r x<r x<r时, x / r x/r x/r必然恒为零,我们顺着求下去,我们会发现 g ( x ) g(x) g(x)有一个神奇的规律,举例来说,当 r = 2 r=2 r=2时, g ( x ) g(x) g(x)的就是 0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , . . . . . 0,0,1,1,2,2,3,3,4,4,..... 0,0,1,1,2,2,3,3,4,4,.....,它每个数字重复的次数正好等于 r r r,那么通过这个性质我们可以把这个问题转化为求一个等差数列,代码如下:

    ll sigma_g(int x) {
    x++;
    ll groups = x / r;
    ll delta = x % r;
    ll sum = groups * (groups - 1) / 2 * r + delta * (groups);
    return sum;
    }
    

    此函数的功能是求 g ( 0 ) + g ( 1 ) + . . . + g ( x ) g(0)+g(1)+...+g(x) g(0)+g(1)+...+g(x)的值,根据刚刚发现的那条性质,我们把这个特殊的等差数列先分个组,看有多少元素是可以通过等差数列求和再与 r r r相乘直接得出,剩下的尾数单独加上。
    根据这个函数,如果我们需要求区间 [ i , j ] [i,j] [i,j] g g g值的和,我们就可以通过 s i g m a _ g ( j ) − s i g m a _ g ( i − 1 ) sigma\_g(j)-sigma\_g(i-1) sigma_g(j)sigma_g(i1)来求,与前缀和的思想相同。

  • 找到了求 f f f g g g区间和的 O ( 1 ) O(1) O(1)做法,我们这个时候就可以考虑怎么将绝对值符号拆开了。按照从小学到大的分类讨论法,讨论绝对值内部的正负性,当绝对值内部大于等于0时,绝对值可以直接拿掉;当绝对值内部小于0时,绝对值符号拿掉后还要将内部的值变为原来的相反数,可分为三种情况:

    • f > g f>g f>g由于我们枚举的是每一个 A [ i ] A[i] A[i],并且求出了以 i i i f f f值的 x x x的范围是在 [ A ( i ) , A ( i + 1 ) − 1 ] [A(i),A(i+1)-1] [A(i),A(i+1)1]之间,说明 f f f值在这个区间上全部都相同.因此如果f值有一个大于等于 g g g的最大值,则整个区间上都有 f > = g f>=g f>=g,也就是说绝对值符号可以直接去掉。
    • 反之,去掉绝对值后将绝对值内部取相反数
    • 当然也有一半 f f f大和一半 g g g大的情况,根据g的区间递增性和f的区间不变性,我们只需要找到使 f = g f=g f=g成立的那一个点,再分别按前面讨论的两种情况进行拆分绝对值即可,具体代码为:
      for (int i = 0; i <= n; i++) {
              //计算以a[i]为f值的x的左右边界
              ll lf, rt;
              if (i < n)
                  lf = a[i], rt = a[i + 1] - 1;
              else
                  lf = a[i], rt = N - 1;
              //如果f值大于g(rt),证明f-g>=0,可以直接去掉绝对值
              if (rt / r <= i) {
                  ll f = i * (rt - lf + 1), g = sigma_g(rt) - sigma_g(lf - 1);
                  error += f - g;
              }
              else if (lf / r > i) {
                  ll f = i * (rt - lf + 1), g = sigma_g(rt) - sigma_g(lf - 1);
                  error += g - f;
              }
              else {
                  //找到使g(x)==f(x)成立的x,此x一定存在因为g(x)是连续递增的
                  ll j = lf;
                  while (j <= rt) {
                      if (j / r == i) {
                          break;
                      }
                      j++;
                  }
                  ll f1 = (j - lf + 1) * i, f2 = (rt - j) * i;
                  ll g1 = sigma_g(j) - sigma_g(lf - 1), g2 = sigma_g(rt) - sigma_g(j);
                  error += f1 - g1 + g2 - f2;
              }
          }
      

完整代码:

#include <iostream>
#include <cmath>
#define ll long long
using namespace std;
const int maxn = 1e5 + 1;
int n, r, N, a[maxn];

//求g(0)+...+g(x)的和
ll sigma_g(int x) {
    x++;
    ll groups = x / r;
    ll delta = x % r;
    ll sum = groups * (groups - 1) / 2 * r + delta * (groups);
    return sum;
}

int main()
{
    cin >> n >> N;
    r = N / (n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    ll error = 0;
    for (int i = 0; i <= n; i++) {
        //计算以a[i]为f值的x的左右边界
        ll lf, rt;
        if (i < n)
            lf = a[i], rt = a[i + 1] - 1;
        else
            lf = a[i], rt = N - 1;
        //如果f值大于g(rt),证明f-g>=0,可以直接去掉绝对值
        if (rt / r <= i) {
            ll f = i * (rt - lf + 1), g = sigma_g(rt) - sigma_g(lf - 1);
            error += f - g;
        }
        else if (lf / r > i) {
            ll f = i * (rt - lf + 1), g = sigma_g(rt) - sigma_g(lf - 1);
            error += g - f;
        }
        else {
            //找到使g(x)==f(x)成立的x,此x一定存在因为g(x)是连续递增的
            ll j = lf;
            while (j <= rt) {
                if (j / r == i) {
                    break;
                }
                j++;
            }
            ll f1 = (j - lf + 1) * i, f2 = (rt - j) * i;
            ll g1 = sigma_g(j) - sigma_g(lf - 1), g2 = sigma_g(rt) - sigma_g(j);
            error += f1 - g1 + g2 - f2;
        }
    }
    cout << error;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值