题目地址:
https://www.acwing.com/problem/content/1457/
某公司招聘,有 n n n个人入围,HR在黑板上依次写下 m m m个正整数 A 1 , A 2 , … A m A_1,A_2,…A_m A1,A2,…Am,然后这 n n n个人围成一个圈,并按照顺时针顺序为他们编号 0 , 1 , 2 , … n − 1 0,1,2,…n−1 0,1,2,…n−1。录取规则是:第一轮从 0 0 0号的人开始,取用黑板上的第 1 1 1个数字,也就是 A 1 A_1 A1。黑板上的数字按次序循环使用,即如果某轮用了第 k k k个,如果 k < m k<m k<m,则下一轮需要用第 k + 1 k+1 k+1个;如果 k = m k=m k=m,则下一轮用第 1 1 1个。每一轮按照黑板上的次序取用到一个数字 A x A_x Ax,淘汰掉从当前轮到的人开始按照顺时针顺序数到的第 A x A_x Ax个人(取数的那个人是第 1 1 1个,然后依次顺时针数)。下一轮开始时轮到的人即为被淘汰掉的人的顺时针顺序下一个人,被淘汰的人直接回家,所以不会被后续轮次计数时数到。经过 n − 1 n−1 n−1轮后,剩下的最后 1 1 1人被录取,所以最后被录取的人的编号与 ( n , m , A 1 , A 2 , … A m ) (n,m,A_1,A_2,…A_m) (n,m,A1,A2,…Am)相关。
输入格式:
输入包含多组测试数据。第一行包含整数
T
T
T,表示共有
T
T
T组测试数据。接下来
T
T
T行,每行包含若干个整数,依次存放
n
,
m
,
A
1
,
A
2
,
…
A
m
n,m,A_1,A_2,…A_m
n,m,A1,A2,…Am,表示一组数据。
输出格式:
输出共
T
T
T行,每行对应相应的那组数据确定的录取之人的编号。
数据范围:
0
<
T
<
10
0<T<10
0<T<10
0
<
m
,
A
x
<
1
0
3
0<m,A_x<10^3
0<m,Ax<103
0
<
n
<
1
0
7
0<n<10^7
0<n<107
考虑普通的约瑟夫问题是怎么做的,例如每次数 m m m个人,第 m m m个人就出列,并且一开始总共有 n n n个人。假设每一轮出列一个人之后,剩余的人都会重新编号,编号也从 0 0 0开始,并且那个出列的人的下一个人的编号恰好是 0 0 0。设 f ( k ) f(k) f(k)是只剩下 k k k个人的情况下,最后留下来的人在当前轮的编号是多少,那么显然 f ( 1 ) = 0 f(1)=0 f(1)=0,我们的目标是求 f ( n ) f(n) f(n)。考虑 f f f的递推关系,如果我们已知 f ( k − 1 ) f(k-1) f(k−1),考虑原来有 k k k个人,这 k k k个人的编号是 0 , 1 , 2 , . . . , m − 1 , m , . . . , f ( k − 1 ) , . . . , k − 1 0,1,2,...,m-1,m,...,f(k-1),...,k-1 0,1,2,...,m−1,m,...,f(k−1),...,k−1(其中 f ( k − 1 ) f(k-1) f(k−1)的位置不知道在哪儿,但只知道它不等于 m − 1 m-1 m−1,否则的话他就出列了),那么 m − 1 m-1 m−1会出列,接下来 m m m会变成下一轮的 0 0 0, m + 1 m+1 m+1会变成下一轮的 1 1 1,以此类推,那么相当于我们要问一个反问题,下一轮变成 f ( k − 1 ) f(k-1) f(k−1)的人,在上一轮编号是什么,显然就是 ( f ( k − 1 ) + m ) % k (f(k-1)+m)\%k (f(k−1)+m)%k,即 f ( k ) = ( f ( k − 1 ) + m ) % k f(k)=(f(k-1)+m)\%k f(k)=(f(k−1)+m)%k,于是我们有了递推方程,递推出 f ( n ) f(n) f(n)即可。
而本题不是每次数 m m m个人,而是每次数 A i A_i Ai个人,因此我们需要知道每轮报的是哪个数,可以这样看,首先 f ( n ) = ( f ( n − 1 ) + A 1 ) % n f(n)=(f(n-1)+A_1)\%n f(n)=(f(n−1)+A1)%n那么以此类推, f ( n − 1 ) = ( f ( n − 2 ) + A 2 ) % ( n − 1 ) f(n-1)=(f(n-2)+A_2)\%(n-1) f(n−1)=(f(n−2)+A2)%(n−1),而对于模 m m m的剩余类群 1 , 2 , . . . , m 1,2,...,m 1,2,...,m对于加法的映射关系是 g ( i + k ) = ( i − 1 + k ) % m + 1 g(i+k)=(i-1+k)\%m+1 g(i+k)=(i−1+k)%m+1,所以有 f ( k ) = ( f ( k − 1 ) + A ( n − k ) % m + 1 ) % k f(k)=(f(k-1)+A_{(n-k)\%m+1})\%k f(k)=(f(k−1)+A(n−k)%m+1)%k代码如下:
#include <iostream>
using namespace std;
const int N = 1e7 + 10;
int a[N], f[N];
int n, m;
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= m; i++) scanf("%d", &a[i]);
f[1] = 0;
for (int i = 2; i <= n; i++)
f[i] = (f[i - 1] + a[(n - i) % m + 1]) % i;
printf("%d\n", f[n]);
}
return 0;
}
每组数据时间复杂度 O ( n ) O(n) O(n),空间 O ( n ) O(n) O(n)。
当然也可以直接用一个变量来递推:
#include <iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int n, m;
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= m; i++) scanf("%d", &a[i]);
int res = 0;
for (int i = 2; i <= n; i++)
res = (res + a[(n - i) % m + 1]) % i;
printf("%d\n", res);
}
return 0;
}
时间一样,空间 O ( 1 ) O(1) O(1)。