【ACWing】1455. 招聘

题目地址:

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,n1。录取规则是:第一轮从 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 n1轮后,剩下的最后 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(k1),考虑原来有 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,...,m1,m,...,f(k1),...,k1(其中 f ( k − 1 ) f(k-1) f(k1)的位置不知道在哪儿,但只知道它不等于 m − 1 m-1 m1,否则的话他就出列了),那么 m − 1 m-1 m1会出列,接下来 m m m会变成下一轮的 0 0 0 m + 1 m+1 m+1会变成下一轮的 1 1 1,以此类推,那么相当于我们要问一个反问题,下一轮变成 f ( k − 1 ) f(k-1) f(k1)的人,在上一轮编号是什么,显然就是 ( f ( k − 1 ) + m ) % k (f(k-1)+m)\%k (f(k1)+m)%k,即 f ( k ) = ( f ( k − 1 ) + m ) % k f(k)=(f(k-1)+m)\%k f(k)=(f(k1)+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(n1)+A1)%n那么以此类推, f ( n − 1 ) = ( f ( n − 2 ) + A 2 ) % ( n − 1 ) f(n-1)=(f(n-2)+A_2)\%(n-1) f(n1)=(f(n2)+A2)%(n1),而对于模 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)=(i1+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(k1)+A(nk)%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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值