2018icpc沈阳-K.Let the Flames Begin (约瑟夫环问题)

题意:

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

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

2019.11.28:发现之前自己当时写的有很多问题没有弄清楚

首先约瑟夫环的递推公式(可以模拟一下递归,先到只剩下最后一个人,然后每一次往前一个状态回递推,求得这个人所在位置–注意这个递推公式求得是当前状态序列的位置下标

 

即约瑟夫环通过递推,从最后的状态得到最初的状态。而由于要问第M个被杀掉的是谁,所以我们的初始状态不是从只剩下最后一人开始(长度为1),而是从(n-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 ≤ 1018,以及min{m,k} 总和不超过 2 × 106,m >= n.

所以对于 m 较大的数(会发现模数大部分情况下远大于kk,即要移动 x次(x很大),才会走完一圈),也就是说可以用乘法代替多次加法。

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;
}

 

posted @ 2019-09-19 11:53  Tianwell  阅读(522)  评论(0编辑  收藏  举报