题意:

转化为经典约瑟夫环问题:

N个人围成一圈,从第一个开始报数,第K个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。

然后现在给出 N(标号1~N)个人,每隔K个将要被杀掉,问第 M 个被杀掉的 标号是多少。(与顺时针或者逆时针无关)。

思路:

基础递推原理可以参考下

博客:https://www.cnblogs.com/jjscm/p/4463555.html

即约瑟夫环通过逆向递推,得到最初的状态。而由于要问第M个被杀掉的是谁,所以我们的初始状态不是从全部杀掉开始(长度为1),而是从(n-m+1)状态开始

而根据约瑟夫环递推式:(表示N个人,第M个出队的人是谁)  f[n][m] = (f[n-1][m-1] + k)%n; 所以转换后为 f[n-m+i] = (f[n-m+i-1] + k) % (n - m + i);

由于题中数据大小为:1 ≤ n, m, k ≤ 10,以及min{m,k} 总和不超过 2 × 10,m >= n.

所以对于 m 较大的数(会发现模数大部分情况下远大于kk),也就是说可以用乘法代替多次加法。

f[a][b] = ans , f[a+x][b+x] = ans + k *x; 而进行取模的条件为:ans + k*x >= a+x 即 x >= (a - ans) / (k-1) ;

然后剩余对 K = 1的特判情况,直接是 M 出队。

于时转换为代码如下:

code:

#include <bits/stdc++.h>
using namespace std;
#define ll long long

int main() {
    int cas = 1;
    int t;
    scanf("%d",&t);
    while(t--) {
        ll n,m,k;
        ll f;
        scanf("%lld%lld%lld",&n,&m,&k);
        printf("Case #%d: ",cas++);
        if(m <= k) {
            f = k % (n - m + 1);
            if(f == 0) f = n - m + 1;
            for(ll i = 2; i <= m; i++)
                f = (f + k) % (n - m + i);
            printf("%lld\n",f);
        } else {
            if(k == 1) printf("%lld\n",m);
            else {
                ll a = n - m + 1, b = 1;
                ll f = k % a, x = 0;
                if(f == 0) f = a;
                while(b + x <= m) {
                    a += x, b += x, f += k * x;
                    f %= a;
                    if(f == 0) f = a;
                    x = (a - f) / (k - 1) + 1;
                }
                f += (m - b) * k;
                f %= n;
                if(f == 0) f = n;
                printf("%lld\n",f);
            }
        }
    }
    return 0;
}
01-19 10:34