题意:
转化为经典约瑟夫环问题:
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 ≤ 1018,以及min{m,k}
总和不超过 2 × 106,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; }