一些函数复合问题

Published: by Creative Commons Licence

  • Tags:

绝对值差

考虑有这样一个序列x1,x2,xn,其中每个整数都不超过106,用这个数列可以唯一确定一个函数f,其接受一个参数x0,这个函数会输出: ||||x0x1|x2|x3|xn|

其代码大概为:

f(x0){
    ans = x0;
    for(int i = 1; i <= n; i++){
        ans = |ans - a[i]|;       
    }
    return ans;
}

现在我们有m个参数a1,a2,,am,其中0ai106,希望计算这m个输入下f的对应输出f(a1),f(a2),,f(am)

其中n,m106

很显然,我们可以在时间复杂度O(nm)内解决,但是会花掉过多的时间。

我们可以定义另外n个函数f1,f2,,fn,其中fk表示由序列后k个元素确定的函数,其代码应该为:

fk(x0){
    ans = x0;
    for(int i = n - k + 1; i <= n; i++){
        ans = |ans - a[i]|;       
    }
    return ans;
}

可以发现这n个函数之间具有递推关系:

fi(j)=fi1(|a[ni+1]j|)

我们可以将函数用一个数组表示,数组下标为i的元素的值表示输入为i时的输出。

我们自然地定义函数f0(x)=x,因此f0函数的数组表示为:

[0,1,2,3,4,...,n]

之后考虑函数f1,其数组表示为:

[a[n],a[n]-1,a[n]-2, ..., 1, 0, 1, 2, ..., n - a[n]]

可以发现f1实际上是将f0的某段前缀翻转并放在了最前面,并删除部分的后缀。

同理我们可以推广出fifi+1的数组形式的变换。

接下来我们考虑如何快速计算出f1,f2,,fn,我们可以递推处理,这里翻转拼接都可以用持久化平衡树(比如treap)来实现。

f=fn,因此我们也得到了f的数组表示,之后的m个请求可以O(1)高效处理。总的时间和空间复杂度为O(nlog2n+m)

阈值递增

给定n个整数x1,,xn。之后给定一个函数f,它的逻辑大概为:

f(a){
    for(int i = 1; i <= n; i++){
        if(a >= x[i]){
            a = a + 1;
        }
    }
    return a;
}

现在给定m,要求计算f(0),,f(m),其中0aim

其中1n,m106

fi表示仅考虑x1,,xi得到的函数。现在我们考虑如何得到fi+1。如果我们把fifi+1看作是长度为m+1的向量,那么可以发现fi是单调递增的向量。而ai+1带来的影响是将fi中所有大于等于ai+1的元素,全部增加1,而这部分元素正好对应fi的某个后缀。

我们可以用线段树维护fi,之后在线段树上二分找到需要自增的后缀,之后一次区间增加1即可得到fi+1,所有操作都是O(log2m)

因此我们可以O(nlog2m+m)得到fn并回答所有的查询。