​ACM-ICPC Shenyang Oniste 2018 K. Let the Flames Begin​

题目

约瑟夫环编号 1 ~ n,问你第 m 个人死的编号。(每次数 k 个人),
1 <= n,m, k <= 1e18,(==m,k最小值小于 1e6)

分析

因为是约瑟夫环问题,复习下递推公式。(约瑟夫环问题都是用公式考虑编号从零开始,如果不是,结果 + 1 即可)

假如有 0 到 n 一共 n+1 个点,ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_#define 表示数到最后的编号,第一个出队的是 ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_#define_02,考虑当前删一个点的状况:相当于 n 个点的规模, 0 号点变为原来 k 号点,(即图二的编号整体移动 k。)

公式表示为:ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_递推_03

即:ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_#define_04

ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_最小值_05


分析要求第 m 个的编号,根据上面的递推过程,有:

ACM-ICPC Shenyang Oniste 2018  K. Let the Flames Begin(约瑟夫环 + 分块)_#define_06

这样就可以递推求解,但是题目范围 1e18,显然不现实。但是题目还给出一个条件 m 和 k 一定有一个比较小,显然要从这里入手。

当 m 较小时,直接暴力就行。

当 k 较小时,m 很大 1e18。相应的 n 很大,那么上面的公式 % n次数就很少,那么可以将好多次 +k 变成 几次 *k。这里处理一下即可。

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
#define fuck(x) cout<<x<<endl
const int N = 1e3 + 10;
const int mod = 998244353;

ll t, n, m, k, ans;

int main(){
int cas = 1;
scanf("%lld\n", &t);
while(t--){
scanf("%lld%lld%lld", &n, &m, &k);
printf("Case #%d: ", cas++);
if(m < k){ // m 比较小
ans = (k - 1) % (n - m + 1); // 递归改递推
for(ll i = n - m + 2; i <= n; i++){
ans = (ans + k) % i;
}
printf("%lld\n", ans + 1);
}else{
if(k == 1){
printf("%lld\n", m);
}else{
ans = (k - 1) % (n - m + 1); // m = 1时, k - 1是第一个出去的
for(ll i = n - m + 2, j = i; i <= n; i = j+1){
ll tmp = (i - 1 - ans) / (k - 1); // 每次前进 k 个,就有 k-1 的间隔
if((i - 1 - ans) % (k - 1) != 0){ // 不能整除的话,会多一
tmp++;
}
if(i + tmp - 1 >= n){ // 如果大于 n ,证明剩下到 n 直接乘就行
ans = (ans + (n - i + 1) * k) % n;
break;
}
ans = (ans + tmp * k) % (i + tmp - 1);
j = i + tmp - 1;
}
printf("%lld\n", ans + 1);
}
}
}
}