1455. 招聘(约瑟夫环升级版问题)

某公司招聘,有 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被录取者的编号
110
23(0+3)%2=1
31(1+1)%3=2
43(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;
}

扩展知识

  1. 如果当m=1时,也就是约瑟夫环问题了,也可以采取空间换时间的策略缩短运行时间(多用例情况下有效)。方法为开辟一个数组记录递推的值,求的时候先判断数组是否有记录,有则返回,无则从以前求的最大的人数(n)开始递归到现在要求的人数n即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值