某公司招聘,有 n 个人入围,HR在黑板上依次写下 m 个正整数 A1,A2,…Am,然后这 n 个人围成一个圈,并按照顺时针顺序为他们编号 0,1,2,…n−1。
录取规则是:
第一轮从 0 号的人开始,取用黑板上的第 1 个数字,也就是 A1。
黑板上的数字按次序循环使用,即如果某轮用了第 k 个,如果 k<m,则下一轮需要用第 k+1 个;如果 k=m,则下一轮用第 1 个。
每一轮按照黑板上的次序取用到一个数字 Ax,淘汰掉从当前轮到的人开始按照顺时针顺序数到的第 Ax 个人。
下一轮开始时轮到的人即为被淘汰掉的人的顺时针顺序下一个人,被淘汰的人直接回家,所以不会被后续轮次计数时数到。
经过 n−1 轮后,剩下的最后 1 人被录取,所以最后被录取的人的编号与 (n,m,A1,A2,…Am) 相关。
输入格式
输入包含多组测试数据。
第一行包含整数 T,表示共有 T 组测试数据。
接下来 T 行,每行包含若干个整数,依次存放 n,m,A1,A2,…Am,表示一组数据。
输出格式
输出共 T 行,每行对应相应的那组数据确定的录取之人的编号。
数据范围
0<T<10,
0<m,Ax<103,
0<n<107
输入样例:
1
4 2 3 1
输出样例:
1
样例解释
样例里只有 1 组测试数据,说的是有 4 人入围(编号 0∼3)。
黑板上依次写下 2 个数字:3、1,那么:
第一轮:当前轮到 0 号,数到数字 3,顺时针数第 3 个人是 2 号,所以淘汰 2 号,下一轮从 3 号开始,目前剩余:0、1、3;
第二轮:当前数到 3 号,取到数字 1,顺时针数第 1 个人是 3 号,所以淘汰 3 号,下一轮从 0 号开始,目前剩余:0、1;
第三轮:当前轮到 0 号,循环取到数字 3,顺时针数第 3 个人是 0 号,所以淘汰 0 号,最后只剩下 1 号,所以录取 1 号,输出 1;
思路分析
如果n和m比较小的话可以使用数组或链表去模拟筛选的过程,然后求出被录取的人的序号。但是对于该题来说不行,因为有两层循环(第一层淘汰n个人循环n,第二层数数模拟m)。即O(n*m)=107*103=1010超时了。
DP解法:
f[n][k] :表示有n个人应聘,且第n次使用的是正整数Ak进行筛选,最终被录取的人的编号。(这是约瑟夫环问题的逆思维)
因为只有1个人的时候录取的永远是0号,有2个人时,被录取的那个人对应有1个人时被录取的那个人偏移了Ak-1个位置(1人时用的是Ak)。有3个人时,被录取的那个人对应有2个人时被录取的那个人偏移了Ak-2个位置。
如样例:4 2 3 1,有4个人,有2个数3、1。
有n个人 | 使用的是正整数Ak | 被录取者的编号 |
---|---|---|
1 | 1 | 0 |
2 | 3 | (0+3)%2=1 |
3 | 1 | (1+1)%3=2 |
4 | 3 | (2+3)%4=1 |
所以递推式为f(n+1,k)=(f(n,(k-1+m)%m)+Ak)%(n+1);
初始值f(1,x)=0,(即只有一个人的时候录取的永远是0号)
所以由题意可知,我们只要求出当n=1时其使用的是那个正整数就可以进行约瑟夫环问题的逆运算。其使用的下标对应关系如下图所示(为了方便对应,所以将A1,A2,…Am下标对应为0-(m-1)):
所以当n=1时对应的下标为x=(0+(n-1))%m;
扩展知识:如果题目改一下,当n个人时使用的是数Am那么当n=1时对应的下标是多少呢?很显然是((m-1)+(n-1))%m,(注意:数Am偏移后的下标为(m-1))。
代码
时间复杂度 O(n)
#include <iostream>
using namespace std;
int num[1005];
int main()
{
int n,m,t;
cin>>t;//输出测试数据的组数
while(t--){
cin>>n>>m;//有n个人入围,m个正整数
for(int i=0;i<m;i++)
cin>>num[i];//输入HR在黑板上依次写下的m个正整数
int res=0;//只有一个人的时候录取的永远是0号
//推断1个人时,使用的是第几个正整数来淘汰人(num的下标从0开始)
int k=(n-1)%m;
//推断后面的人那个人被录取(需要推n-1次),因为只有一个人的情况是已知的
//从2个人递推到n个人
for(int i=2;i<=n;i++){
//因为淘汰人的时候num的下标是正序使用的(0-(m-1)->0)
//所以递推谁没有被淘汰时则需要逆序使用num的下标
//因为(n-1)是有可能大于m的,所以执行(n-1)次k-1操作k是可能变负数的
//(k-1+m)加上一个m后对正数的求余结果无影响,
//但是对负数而言可以将其挪到正数区间
k=(k-1+m)%m;
//n个人求职时被录取的那个人,
//相对于n-1个人求职时被录取的那个人偏移了num[k]个位置
res=(res+num[k])%i;//n是动态变化的,就是此处的i
}
cout<<res<<endl;//输出n个人求职时被录取的那个人的编号
}
return 0;
}
扩展知识
- 如果当m=1时,也就是约瑟夫环问题了,也可以采取空间换时间的策略缩短运行时间(多用例情况下有效)。方法为开辟一个数组记录递推的值,求的时候先判断数组是否有记录,有则返回,无则从以前求的最大的人数(n)开始递归到现在要求的人数n即可。