HDU_3089_约瑟夫环快速递推
链接:
http://acm.hdu.edu.cn/showproblem.php?pid=3089
题意:
约瑟夫环问题。给出
n
n
n 个人,从
1
1
1 到
n
n
n 编号,
n
n
n 个人围成一个圈。第一个人从
1
1
1 开始报数,数到
k
k
k 的人杀掉,然后从下一个人开始重新报数,如此循环,直到只剩下一个人,问幸存的人的编号。
思路:
传统约瑟夫环问题:可以这么想,每减少一个人,相当于把序列整体循环左移
k
k
k 位,也就是说,如果设存有
i
i
i 个人时幸存者的序号为
f
(
i
)
f(i)
f(i) ,那么如果我们已知还留存有
i
−
1
i-1
i−1个人时幸存者的序号
f
(
i
−
1
)
f(i-1)
f(i−1) ,那么逆推状态
f
(
i
)
=
(
f
(
i
−
1
)
+
k
)
%
i
f(i) = (f(i-1)+k)\%i
f(i)=(f(i−1)+k)%i (
+
k
+k
+k 是右移,因为会超范围,所以模
i
i
i,使得数据都在范围
0
→
i
−
1
0 \to i-1
0→i−1,因为取模我们从
0
0
0 开始报数,求得结果加一即可)。只剩下一个人时,
f
(
1
)
=
0
f(1) = 0
f(1)=0 。时间复杂度
O
(
n
)
O(n)
O(n)。
状态转移方程:
f
(
i
)
=
{
0
i
=
1
(
f
(
i
−
1
)
+
k
)
%
i
i
∈
2...
n
f(i)= \begin{cases} 0 & \text { $i = 1$ } \\ (f(i-1) + k)\%i & \text{ $i \in 2...n$ } \end{cases}
f(i)={0(f(i−1)+k)%i i=1 i∈2...n
幸 存 者 编 号 : a n s = f ( n ) + 1 幸存者编号: ans = f(n) + 1 幸存者编号:ans=f(n)+1
本题:但是这道题的 n n n 非常大,看似无法用上述算法。但是这题的 k k k 很小,只有 1000 1000 1000。观察上式可以发现 k k k 比较小的时候递推后期可能加若干个 k k k 才会取模。那么我们是不是可以多步递推来加速递推式?没错,就是这样。
- 当 f ( i − 1 ) + k ≥ i f(i-1)+k \geq i f(i−1)+k≥i 时,直接递推。
- 当 f ( i − 1 ) + k < i f(i-1)+k < i f(i−1)+k<i 时,加速优化。
那么可以加速多少步呢?设加速步数为
x
x
x。
{
f
(
i
)
=
(
f
(
i
−
1
)
+
k
)
%
i
.
.
.
f
(
i
+
x
−
1
)
=
(
f
(
i
−
1
)
+
x
∗
k
)
%
(
i
+
x
−
1
)
\begin{cases} f(i) = (f(i-1)+k)\%i \\ \ \ \ \ . \\ \ \ \ \ . \\ \ \ \ \ . \\ f(i+x-1) = (f(i-1) + x*k)\%(i+x-1) \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧f(i)=(f(i−1)+k)%i . . .f(i+x−1)=(f(i−1)+x∗k)%(i+x−1)
设
f
(
i
−
1
)
=
a
n
s
f(i-1) = ans
f(i−1)=ans
则 a n s + x ∗ k < i + x − 1 ans + x*k < i+x-1 ans+x∗k<i+x−1 时,不会取模,这时加速递推( x < i − a n s − 1 k − 1 x < \frac{i-ans-1} {k-1} x<k−1i−ans−1)
注意:
- 当 i − a n s − 1 i-ans-1 i−ans−1 能整除 k − 1 k-1 k−1 时, x = i − a n s − 1 k − 1 − 1 x = \frac{i-ans-1} {k-1}-1 x=k−1i−ans−1−1,否则 x = i − a n s − 1 k − 1 x = \frac{i-ans-1} {k-1} x=k−1i−ans−1
- 加速递推时, i + x − 1 i+x-1 i+x−1 可能会大于等于 n n n ,这时直接终止。
A
C
代
码
AC代码
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, k;
LL ans, x;
int main(int argc, char const *argv[])
{
while(cin>>n>>k) {
if(k == 1) {
cout<<n<<endl;
continue;
}
ans = 0;
for (LL i = 2; i <= n; i+=x) {
if(ans + k >= i) { // 直接递推
x = 1;
}
else { // 加速优化
if((i-ans-1)%(k-1) == 0) {
x = (i-ans-1)/(k-1)-1;
}
else {
x = (i-ans-1)/(k-1);
}
if(i+x-1 >= n) {
ans = ans + (n-i+1)*k;
break;
}
}
ans = (ans + k * x)%(i+x-1);
}
cout<<ans+1<<endl;
}
return 0;
}