问题描述
栋栋正在和同学们玩一个数字游戏。
游戏的规则是这样的:栋栋和同学们一共n个人围坐在一圈。栋栋首先说出数字1。接下来,坐在栋栋左手边的同学要说下一个数字2。再下面的一个同学要从上一个同学说的数字往下数两个数说出来,也就是说4。下一个同学要往下数三个数,说7。依次类推。
为了使数字不至于太大,栋栋和同学们约定,当在心中数到 k-1 时,下一个数字从0开始数。例如,当k=13时,栋栋和同学们报出的前几个数依次为:
1, 2, 4, 7, 11, 3, 9, 3, 11, 7。
游戏进行了一会儿,栋栋想知道,到目前为止,他所有说出的数字的总和是多少。
输入格式
输入的第一行包含三个整数 n,k,T,其中 n 和 k 的意义如上面所述,T 表示到目前为止栋栋一共说出的数字个数。
输出格式
输出一行,包含一个整数,表示栋栋说出所有数的和。
样例输入:3 13 3 样例输出:17
样例说明:栋栋说出的数依次为1, 7, 9,和为17。
数据规模和约定:1 < n,k,T < 1,000,000;
分析:
我们看一下报数时会出现的数字(不做取余处理,上面是报数,下面是差值):
1 2 4 7 11 16 22 29 37 46 ……
1 2 3 4 5 6 7 8 9 ……
我们单独看一下栋栋的报数: 1 7 22 46……
不难发现这些数据服从以下规律:
1
1 + ( 1 + 2 + 3 ) = 7
1 + ( 1 + 2 + 3 ) + ( 4 +5 + 6 ) = 22
由此可以得到栋栋报数的一个通项公式:num = 1 + n (n+1) / 2 (其中n表示栋栋报数时对应的序号-1,显然n取值是:0,3,6,9,……)
知道了通项公式,以及总的循环次数,直接循环求和就是
但是,这样的下场是:拿不到满分
为什么?
仔细看题,T和k的取值最大会接近100 0000,这时候如果仅仅只是利用上面的这个公式来求每次的报数num并求和然后再取余,则num总会在某个值时发生溢出现象(num的数量级极限会取到100 0000^4,显然溢出了long long),这时后面所有的num都将错误。所以这个方案是不可行的
为了避免这个溢出问题,我们应该想到利用前一次num的数值与下一次num的数值之间的差值来完成对num的变化,即利用这个差值来实现数据的更新(因为这之间的差值不会超过long long)
于是我们需要来推算一下(或者说来求出)这个差值量与序数n的关系,即求出这个函数。我们假设,有N个同学参与数字游戏,那么对于一个周期(N)而言
栋栋两次报数的差值是一个关于n的函数:
△x=1+(n+N) [(n+N)+1] / 2 - [ 1+n(n+1) / 2 ] = N(N+2*n+1)/2
这样就可以利用上面的公式,通过上一次num的值,用加法来把下一次的num计算出来,然后取余,叠加到sum中(这样显然是不会产生数据溢出的现象的)
下面给出本题的完整代码:
#include
using namespace std;
int main()
{
int n,k,T;
cin>>n>>k>>T;
long long sum=0,i=0,x=1;//sum是最后的总和,i是东东报数时的序号(从0开始),x是第i-1次东东报数后取余再加上第i-1到第i次之间的差值的值 (即第i次的值)
while(T--)
{
sum+=x;
x+=(n+2*i+1)*n/2;
x%=k;
i+=n;
}
cout<<sum<<endl;
return 0;
}