【gym 101955K】Let the Flames Begin(约瑟夫环问题)
题目链接
大意是 n n n个人围成一圈(ID依次为 1 ∼ n 1\sim n 1∼n),每 k k k个人踢掉1个,求第 m m m个被踢掉的人的ID。
1. O ( k log n ) O(k\log n) O(klogn)解法(未AC)
参考知乎回答
按该回答中的方式进行编号,第 m m m个被踢掉的人编号为 m k mk mk。
n = 10 , k = 3 n=10, k=3 n=10,k=3的情况如图所示:
可知编号 m k + d mk+d mk+d的人下一次编号为 n + m ( k − 1 ) + d ( 1 ≤ d < k ) n+m(k-1)+d (1\le d<k) n+m(k−1)+d(1≤d<k)。
令 N = n + m ( k − 1 ) + d N=n+m(k-1)+d N=n+m(k−1)+d,则 m = [ N − n − 1 k − 1 ] m=[\frac{N-n-1}{k-1}] m=[k−1N−n−1], N ′ = m k + d = N − n + [ N − n − 1 k − 1 ] N'=mk+d=N-n+[\frac{N-n-1}{k-1}] N′=mk+d=N−n+[k−1N−n−1]。
用上述式子不断迭代,最后求出的就是该玩家的id。
记 k n + 1 − N = D kn+1-N=D kn+1−N=D,则 D ′ = ⌈ D k k − 1 ⌉ D'=\lceil\frac{Dk}{k-1}\rceil D′=⌈k−1Dk⌉。
复杂度为 O ( k log n ) O(k\log n) O(klogn)。
可以写出如下代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f(ll n, ll m, ll k){ // n-people, m-th, k-cycle
__int128 D = (__int128)k * (n - m) + 1;
while((__int128)k * n + 1 - D > n){
D = (D * k + k - 2) / (k - 1);
}
return (__int128)k * n + 1 - D;
}
int main(){
int T, cas = 0;
scanf("%d", &T);
ll n, m, k;
while(T--){
scanf("%lld%lld%lld", &n, &m, &k);
printf("Case #%d: %lld\n", ++cas, f(n, m, k));
}
return 0;
}
但是无法通过这题。
2. O ( min ( m , k log n ) ) O(\min(m,k\log n)) O(min(m,klogn))解法
记 f ( n , m , k ) f(n,m,k) f(n,m,k)为问题的答案(0和 n n n等价),则有递推式 f ( n , m , k ) = k + f ( n − 1 , m − 1 , k ) m o d n f(n,m,k)=k+f(n-1,m-1,k) \mod n f(n,m,k)=k+f(n−1,m−1,k)modn。
复杂度 O ( m ) O(m) O(m)。
若 n > k n>k n>k,那么上述方法中多次取模是没有必要的,可以改为批量处理:
f ( n , m , k ) = k x + f ( n − x , m − x , k ) m o d n ( k x + f ( n − x , m − x , k ) ≤ n + k − 1 ) f(n,m,k)=kx+f(n-x,m-x,k) \mod n\\(kx+f(n-x,m-x,k)\le n+k-1) f(n,m,k)=kx+f(n−x,m−x,k)modn(kx+f(n−x,m−x,k)≤n+k−1)
复杂度 O ( min ( m , k log n ) ) O(\min(m,k\log n)) O(min(m,klogn))。
AC代码如下:(要写成非递归形式,否则栈空间不够)
#include <bits/stdc++.h>
#define rep(i, l, r) for(int i=l; i<=r; ++i)
#define per(i, r, l) for(int i=r; i>=l; --i)
#define ll long long
using namespace std;
ll f(ll n, ll m, ll k){
if(k == 1) return m;
ll ret = 0, x = 1, t = m;
while(t){
t -= x;
ret = (k * x + ret) % (n - t);
if(!ret) ret = n - t;
x = min(t, (n - t - ret) / (k - 1) + 1);
}
return ret;
}
int main() {
int T, cas=0;
scanf("%d",&T);
while(T--) {
ll n, m, k;
scanf("%lld%lld%lld", &n, &m, &k);
printf("Case #%d: %lld\n", ++cas, f(n, m, k));
}
return 0;
}