求一个随着数字增加,每次累加的值呈抛物线状的递增数列!

我可以这样描述这个数列,这个数列起始值为100,f(0)=100,长度为200,这个数列是递增的,递增的速度先快后慢。当到100时每次累加的值为最大值,随后累加值逐步递减,直到200时,也就是数列的最后一位时,累加值为0。求该数列计算公式,其中系数k控制数列跳跃幅度,从而来影响数列最后一位值的大小。

———————————
截止当前已回答的答案我都进行了认真的分析和思考,在此感谢各位的关注。
其实我想要的是一个公式,这个公式是给出数列的下标n计算出该位置的值,即 f(n)=x。
举个栗子,该数列最简单的形式为累加量为1,即数列临近两个累加值相差为1, 如f(0)为起始即
f(0)=100;
f(1)=101,fˊ(1)=1;
f(2)=103,fˊ(2)=2;
f(3)=106,fˊ(3)=3;
f(4)=110,f'(4)=4;
f(5)=115,f'(5)=5;
从以上可以看出该数列前6位的值分别为100,101,103,106,110,115。
累加值分别为1,2,3,4,5。累加量为1。

公式为:
f(n)=f(n-1)+f'(n),因为f'(n)=n,所以公式可以简化为f(n)=f(n-1)+n;

在n <99的情况下
f(n)=100+(n+1)*n/2,f'(n)=n;

f(99)=100+100*99/2=5050,f'(99)=99,
此时累加值99为最高点;

f(100)=5050+98=5148;
f(101)=5148+97=5245;
f(102)=5245+96=5341;
f(199)=?
———————————
以上示例为累加量为1的情况,我现在需要的是一个公式,在该公式下,累加量(系数)可以是任何自然数,通过数列下标求出该位置的数列的值即f(n)=x;
需要强调两点,第一该数列是递增数列,这个条件一定要满足,第二就是累加量一定是连续的,呈抛物线态。


以下为对回答的解析:
@边城 算法解析:
为了验证稍微对代码进行了调整:

function createIncrementGenerator(k) {
    return function (x) {
        const t = x / 100 - 1;
        return k - k * t * t;
    };
}
const k = 1000;  // 幅度参数
const getIncrement = createIncrementGenerator(k);  
let n = 100;
var tmp = 100;
for (let i = 0; i < 200; i++, n += Math.round(getIncrement(i))) {
  console.log("f("+i+")="+n+",f'("+i+")="+(n-tmp));
  tmp=n;
}

当K等于100时,增量最大值为100,在最高点出现了15次;
当K等于200时,增量最大值为200,在最高点出现了11次;
当K等于500时,增量最大值为500,在最高点出现了7次;
当K等于800时,增量最大值为800,在最高点出现了5次;
当K等于1000时,增量最大值为1000,在最高点出现了5次;
当K等于2000时,增量最大值为2000,在最高点出现了3次;
当K等于5000时,增量最大值为5000,在最高点出现了3次;
从结果可以看出,K值其实就是增量的最大值,这个设定挺好便于理解,经过验证当K小于1300时,在最高点出现相同增量次数大于3,这样就趋于直线,即会出现超过3个数的增量是相同的。


0326更新
关于@边城的算法,再进行分析,通过交流和分析得知:出现直线是因为取整造成。在这种情况下,如要使用整数数列,就需要增大K的值,这样就可以解决这个问题。
通过观察f'(0)~f'(199)数列的规律,确定是开口向下的抛物线,这个没有问题。但是如果将f'(0)~f'(199)数列的增量再进行组合即f''(0)~f''(199)其实并不是均匀分布,呈一定的规律递减,未再深入分析,图形为斜线。

    function createIncrementGenerator(k) {
        return function (x) {
            const t = x / 100 - 1;
            return k - k * t * t;
        };
    }
    const k = 8000;  // 幅度参数
    const getIncrement = createIncrementGenerator(k);
    let n = 100;
    let tmp1 = 100, tmp2 = 100,tmp3=100;
    for (let i = 0; i < 200; i++, n += Math.round(getIncrement(i))) {
        tmp2 = n-tmp1;
        document.write("f("+i+")="+n+", f'("+i+")="+tmp2+" ,f''("+i+")="+(tmp2-tmp3)+"<br>");
        tmp1=n;
        tmp3=tmp2;
    }

接下来分析@hfhan的算法,根据作者要求,将原代码进行了改造增加了K因子,同时为了方便观察对输出也进行了改动,代码如下:

    let num = 100, count = 199, x = 0;
    let tmp1 = 0, tmp2 ,tmp3=0;
    let k = 2; //// 幅度参数
    while (x <= count) {
        num += k * (-x*x + count*x);
        x++;
        tmp2 = num-tmp1;
        document.write("f("+x+")="+num+", f'("+x+")="+tmp2+" ,f''("+x+")="+(tmp2-tmp3)+"<br>");
        tmp1=num;
        tmp3=tmp2;
    }

该算法更简洁一点。通过观察数列的增幅较大,K的控制幅度直接影响的是f''的增幅,即f''(n)=2*K,且f''(0)~f''(199) 是均匀分布,增量递减,增幅为定值,图形为斜线。


0327 更新
本次分析@未觉雨声的算法,为了不破坏原方法,我在外部进行调用(不考虑性能),另外对原方法中对返回值取整,代码如下:

    function f(pos, size = 199, start = 100, ratio = 100) {
        const center = (size + 1) / 2;
        const speed = i => ratio - ratio * ((i / center - 1) ** 2);
        let prev = start
        for (let i = 0; i <= pos; ++i) {
            prev += speed(i)
        }
        return Math.round(prev);
    }

    let size = 200, start = 100, K = 8000;
    let num;
    let tmp1 = 100, tmp2 = 100, tmp3 = 0;
    for (let i = 0; i <= size; ++i) {
        num = f(i, size, start, K);
        tmp2 = num-tmp1;
        document.write("f("+i+")="+num+", f'("+i+")="+tmp2+" ,f''("+i+")="+(tmp2-tmp3)+"<br>");
        tmp1=num;
        tmp3=tmp2;
    }

通过对结果的观察,也满足本题目标,递增数列,f'呈抛物线,算法基本上与@边城算法相同,因子K也是增量的最大值,即抛物线的最高点,算法有轻微差异,见下截图
image.png


接下来@xdsnet的公式,虽然没有给出具体的算法,但是通过回复的内容进行分析,其思路与@边城的算法基本相同,在这里不做过多赘述,再次感谢@xdsnet。


总结:

至此,感谢各位对本问题的关注,感谢@边城对算法的详尽分析,感谢@xdsnet的互动讨论,同时也感谢@未觉雨声和@hfhan提供的算法。
其实一些问题在开始的时候并没有考虑清楚,可以说是在讨论和思考的过程中发现的,比如在实际场景中需要使用的是整数数列,这个在前面确实没有写清楚,还有就是对f''(0)~f''(199)的分布状态也没有考虑到,这个只是在各位算法出来之后才发现,经过对实际场景需求进行分析,f''的均匀分布更适合需求,只是@hfhan的数列增幅幅度较大,但是对目前来说已经超出了我的预期。


0329 更新
补充分析@边城 整数递归算法
该算法虽然把一些条件在算法中写死,但是对数列的控制更精准,通过对打印的结果分析,f'为抛物线,且均匀分布,增幅为k,即f''(n)=|k| 为常量。令我惊奇的是这个算法居然实现了正文示例的数列。

    function f(n, m = 1) {
        if (n <= 0) { return 100; }
        if (n > 198) { return 0; }
        if (n < 100) {
            return f(n - 1, m) + n * m;
        } else {
            return f(n - 1, m) + (198 - n) * m;
        }
    }

    let K = 10;
    let num;
    let tmp1 = 100, tmp2 = 100, tmp3 = 0;
    for (let i = 0; i <= 200; ++i) {
        num = f(i, K);
        tmp2 = num-tmp1;
        document.write("f("+i+")="+num+", f'("+i+")="+tmp2+" ,f''("+i+")="+(tmp2-tmp3)+"<br>");
        tmp1=num;
        tmp3=tmp2;
    }

再次感谢@边城提供的算法!


至此,本问题可以关闭了,原打算使用@hfhan的算法,但是今天却发现@边城的整数数列的算法更贴近我的需要。
把最真的祝福化作风,吹送到每一位帮助我的人的身边,把最诚的问候变成雨,飘散到每一位帮助我的人的窗前,把我的感谢化作万语千言,为你送来的最真诚的谢意,平安幸福。


在文中如果有描述错误,或者理解有误请告知,感谢~

阅读 4k
4 个回答

回答二(整数递归)

难道说你想要的是一个递归函数?

按你的说法,f(99) 就是顶点,之后增就变成了 98,也就是说 f(100) = f(99) + 98f(101) = f(100) + 97 …… 即 f(n) = f(n - 1) + (198 - n),这样算下来 n 到不了 200,到 198 的时候,增量就已经是 0 了。

// .js
function f(n, m = 1) {
    if (n <= 0) { return 100; }
    if (n > 198) { return 0; }
    if (n < 100) {
        return f(n - 1, m) + n * m;
    } else {
        return f(n - 1, m) + (198 - n) * m;
    }
}

[1, 2, 3, 99, 100, 101, 198, 199, 200]
    .forEach(n => console.log(f(n), f(n, 2)));


// 101 102
// 103 106
// 106 112
// 5050 10000
// 5148 10196
// 5245 10390
// 9901 19702
// 0 0
// 0 0

回答一(二次函数抛物线)

先说增值部分, 这部分是从 0 开始,到 100 最高,到 200 回到 0 —— 可以想像这个曲线,而且很容易判断出来是这个二次函数,其顶点在 (100, k),而且经过了 (0, 0) 和 (200, 0) 这两个点。

captured-1.gif

如果抛物线的顶点在 (h, k),那么这个抛物线的顶点式为:

$$ y = a(x - h)^2 + k $$

代入 (100, h),得

$$ y = a(x - 100)^2 + k $$

因为,它过 (0, 0) 点,代入 x,y 得

$$ \begin{align} &0 = a(0 - 100)^2 + k\\ \Rightarrow&a=-\frac{k}{10000} \end{align} $$

在顶点式中代入 a 可得方程(用 f(x) 表示 y)

$$ \begin{align} & f(x) = -\frac{k}{10000}*(x - 100)^2+k\\ \Rightarrow & f(x)=-k(\frac{x}{100} - 1)^2+k \end{align} $$

这里 k 就是一个可调节参数,可以用于控制幅度(如动图)

剩下的就是一个累加循环了,程序如下(JavaScript 描述,数列元素不保证整数)

function createIncrementGenerator(k) {
    return function (x) {
        const t = x / 100 - 1;
        return k - k * t * t;
    };
}

const k = 200;  // 幅度参数
const getIncrement = createIncrementGenerator(k);  
let n = 100;
const list = [];
for (let i = 0; i < 200; i++, n += getIncrement(i)) {
    list.push(n);   // 如果需要整数,对 n 取整
}

console.log(list.slice(0, 5));
// [ 100, 103.97999999999999, 111.9, 123.72, 139.4 ]
console.log(list.slice(-5));
// [ 26726.6, 26742.28, 26754.1, 26762.019999999997, 26765.999999999996 ]

再来段 C# 的代码(取整了的)

using System;
using System.Collections.Generic;
using System.Linq;
using static System.Console;

public class Program
{
    public static void Main()
    {
        Func<int, double> createIncrementGenerator(double k) {
            return (int x) => {
                var t = x / 100.0 - 1;
                return  k - k * t * t;
            };
        }
        
        var k = 200.0;
        var getIncrement = createIncrementGenerator(k);
        var n = 100.0;
        List<int> list = new();
        for (var i = 0; i < 200; i++, n += getIncrement(i)) {
            list.Add((int) Math.Round(n));   // 这里是按四舍五入取整
        }
        
        WriteLine(string.Join(", ", list.Take(5)));
        // 100, 104, 112, 124, 139
        WriteLine(string.Join(", ", list.TakeLast(5)));
        // 26727, 26742, 26754, 26762, 26766
    }
}

由题可知抛物线方程为:y = -x*x + 200*x
也就是加速度a的值是y
不知道你说的系数n控制数列跳跃幅度是指什么

let num = 100, count = 200, x = 0
while(x <= count){
    num += -x*x + count*x
    x++
}

console.log(num)

设函数f(x),假设f(x)是连续可导的,根据楼主要求有
f(0)=100
f'(100)有最大值
f'(200)=0
且f(x)在[0,200] 是单调递增的

f'(x) 在[0,100]单调递增,f'(x)在[100,200]单独递减。

所以要得出f(x),其实有多种办法

  1. 比如f'(x)是一个分段函数,

    1. f'(x)=2x,(x∈[0,100])
    2. f'(x)=400-2x,(x∈[100,200])
  2. 比如f'(x)是一个二次函数,比如f'(x)=-x(x-200)

这样已知函数导数,就可以通过不定积分和特征点即可得出原函数关系。

比如对应上面:

  1. 分段函数f(x):

    1. f(x)=x^2 +C1 , (x∈[0,100])
    2. f(x)=400x-x^2 +C2 ,(x∈(100,200])
    3. 又因为f(0)=100,所以C1=100,又因为函数是连续的,所以C2= -19900
  2. f(x)=-x^3 / 3 +100x^2 +C , (x∈[0,200])又因为f(0)=100,所以C=100

看了楼主最新解释,感觉楼主前面描述都没有说清楚,我转述一下,看看是否合理:

  1. 有一个函数f(n)=f(n-1)+y(n),0<=n<200,n∈N
  2. 其中f(0)=100,且f(n)在定义域内单调递增,即y(n)>=0
  3. y(200)=0,y(100)有区域内最大值
  4. y(n)在[0,100]单独增加,在(100,200)单调减小

另外楼主希望y(n)大致能符合抛物线规律,且y(n)能表示为 y(n)=k* s(n) 的形式,这样好通过控制k来调整y(n)。

其实对于上面的解答,我前面都提到过,只是没有这么表示而已,而且对于k,其实前面两种都可以直接用一个K(K>0) 去调整的,直接整个乘就可以。只是这样处理不能保障f(n)的结果为整数,需要近似一下。

这个和起始值没啥关系,就是一个对速度求解方程的数学题。

设方程为 ax² + bx + c = y,其中 x 为位置(索引 0 ~ 199,方便计算先按 0 ~ 200),y 为速度。

跳跃幅度 n 本质控制的是当 x = 100(中点)时 y 最大可以到多少,比如这里假设 y 最大时为 400,可得以下等式:

ax² + bx + c = y
c = 0 (x = 0, y = 0)
10000a + 100b + c = 400 (x = 100, y = 400)
40000a + 200b + c = 0 (x = 200, y = 0)

可得 a = -0.04, b = 8, c = 0,即 -0.04x² + 8x = y

得到速度的方程式解法后,算具体累加值就简单了。

——————————————————

引用一下 @边城 的公式

image.png

其中 x / 100 的 100 是中间位置决定的

大小取奇数 199,这样中间位置 100 对应的正好的速度峰值,0 和 199 正好的 0

function f(pos, size = 199, start = 100, ratio = 100) {
  const center = (size + 1) / 2
  const speed = i => ratio - ratio * ((i / center - 1) ** 2)

  let prev = start

  for (let i = 0; i <= pos; ++i) {
    prev += speed(i)
  }

  return prev
}

f(1) // 101.99000000000001
f(2) // 105.95000000000002
f(3) // 111.86000000000003
f(100) // 6816.499999999999
f(199) // 13433

对于 size 是偶数的情况,例如 200(0 ~ 199) 的时候,可以让索引为 99 和 100 均为最大速度(增量),那样速度的抛物线仍然是对称的,0 和 199 都是 0(具体代码就不贴了)

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题