历届试题 数字游戏
时间限制:1.0s 内存限制:256.0MB
问题描述
栋栋正在和同学们玩一个数字游戏。
游戏的规则是这样的:栋栋和同学们一共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;
【思路】
模拟做可以水走66分的分数,思路比较直观,但时间复杂度到了O(n * T),这题数据量如此之大,要过后面两个数据点就不可能了
模拟代码:
/*
相当于说的数字对k取模
数据量偏大,最后的sum要用longlong保证不溢出
模拟超时。。66分
*/
#include<iostream>
using namespace std;
#define ll long long
int n, k, T;
int main()
{
//用scanf提速,还是不行
scanf("%d%d%d", &n, &k, &T);
int dis = 1; //表示每次报的数与前一个数的间隔dis。初始为1
int num = 1; //报的数字,初始值也是为1
int cnt = 0; //记录冻冻报出的数个数
ll sum = 0; //记录冻冻报数的总和
int i = 1;
while(cnt < T)
{
cnt++;
sum += num;
for(i = 1;i <= n;i++){num = (num + dis++) % k;}
}
cout << sum << endl;
return 0;
}
那么怎么才能通过呢?
网上查阅相关资料后,发现唯一一条路就是数学推导公式了。。orz
是这样的,我们的结果其实只依赖于冻冻所报的数字,因此,我们如果能用某种数学公式直接将冻冻每次报的数算出来,那么我们就能在O(T)的时间复杂度里面解决问题了!
推导发现如下规律:
比如第一个测试样例:
冻冻第一个数:1
冻冻第二个数:(1 + (1 + 2 + 3)) % k = 7
冻冻第三个数:(7 + (4 + 5 + 6)) % k = 9
...
这个道理细细一想也能明白,间隔dis每次都在自增1
所以公式应该是
Xk = (Xk-1 + (dis * n + SUM(1, n - 1))) % K;
公式推出来了,代码就没问题了吗?
注意另一个坑点:本题数据量很大,所有参与计算的变量都要用long long,否则溢出到你怀疑人生
AC代码:
/*
本题模拟就算做到极致,也有时间复杂度O(n * T),AC不了
只能通过数学推导,每次直接算出冻冻的数才行
*/
#include<iostream>
using namespace std;
#define ll long long
const int maxn = 1000005;
ll n, k, T;
ll s[maxn];
void init() //预处理和数组
{
ll i;
s[1] = 1;
for(i = 2;i <= maxn;i++)
s[i] = s[i - 1] + i;
}
int main()
{
cin >> n >> k >> T;
ll dis = 1; //记录每次所报数字与上一个数字的间隔
ll num = 1;
ll sum = 1;
ll cnt = 1;
init();
while(cnt < T)
{
cnt++; //第二次开始
num = (num + dis * n + s[n - 1]) % k; //冻冻第二个报的数字
sum += num;
dis += n;
}
cout << sum << endl;
return 0;
}