And Then There Was One

题目link:https://www.luogu.com.cn/problem/UVA1394


Part0:

题意简化:

约瑟夫问题。

给定 $n$ 个编号由 $1$ $ ext{~}$ $n$ 的人,最开始杀掉第 $m$ 个人,接着每数 $k$ 个人就杀掉最后数到的那个,求最后剩下的人的编号。


Part1:

首先容易得出一个 $O(n^2)$ 的用数组循环暴力模拟的算法,但是发现这个算法会枚举到已经死掉的人,因此考虑用链表优化。


Part2:

链表可以 $O(1)$ 进行删除,因此可以 $O(nk)$ 地解决这个问题,但是显然 $n$,$k$ 最大为 $10^4$,$O(nk)$ 过不了此题,所以考虑用递推解决。

这里也给出链表做法的代码:

 1 #include <cstdio>
 2 
 3 const int MAXN = 1e4;
 4 
 5 int n, k, m;
 6 
 7 struct Node {
 8     int nxt, pre, val, dead;
 9 }lst[MAXN + 10];
10 
11 void remove(int id) {
12 
13     lst[id].dead = 1;
14     lst[lst[id].nxt].pre = lst[id].pre;
15     lst[lst[id].pre].nxt = lst[id].nxt;
16 
17     return;
18 }
19 
20 int main() {
21 
22     while(233) {
23 
24         scanf("%d %d %d", &n, &k, &m);
25         if(!n && !k && !m) return 0;
26 
27         for(int i = 1; i <= MAXN; ++i) {
28             lst[i].dead = 0, lst[i].nxt = 0, lst[i].pre = 0, lst[i].val = 0;
29         }
30         lst[1].nxt = 2, lst[1].pre = n, lst[1].val = 1;
31         lst[n].nxt = 1, lst[n].pre = n - 1, lst[n].val = n;
32         for(int i = 2; i < n; ++i) {
33             lst[i].pre = i - 1, lst[i].nxt = i + 1, lst[i].val = i;
34         }
35 
36         remove(m);
37         int numKill = 1;
38         int lastKill = m;
39         while(++numKill != n) {
40             for(int i = 1; i <= k; ++i) {
41                 lastKill = lst[lastKill].nxt;
42             }
43             remove(lastKill);
44         }
45 
46         for(int i = 1; i <= n; ++i) {
47             if(!lst[i].dead) {
48                 printf("%d
", i);
49                 break;
50             }
51         }
52 
53     }
54 
55     return 0;
56 }
链表代码

Part3:

设 $f_i$ 表示 $i$ 个编号为 $ ext{0 ~ i - 1}$ 的人,每次都数到 $k$ 就杀(注意这里是先杀 $k$ $-$ $1$ 不是先杀 $m$ $-$ $1$ 且 为了方便,编号换了一下)。

则递推式为:

$f_i$ $=$ $(f_{i - 1}$ $+$ $k)$ $\%$ $i;$


Part3.1:

举个例子:

对于一个 $ ext{n = 6,k = 3}$ 的例子。

容易发现第一个死掉的人就是 $ ext{2}$ 号。

以此类推容易得出最后剩下的人就是 $ ext{0}$ 号。

那么对于一个 $ ext{n = 5,k = 3}$ 的例子。

 容易得出第一个死的人还是 $ ext{2}$ 号。

 以此类推可以得出最后剩下的人就是 $ ext{3}$ 号。


Part3.2:

对递推式的理解为,在 $ ext{n = 6,k = 3}$ 的图上杀掉第一个人 $ ext{2}$ 号时,从 $ ext{2}$ 号的下一个 $ ext{3}$ 号开始从 $0$ 顺序重新编号。

这样一来,容易发现新的编号(蓝色编号),比原来的编号都大了 $k$,而这个新的编号恰好就是 $ ext{n = 5,k = 3}$ 时的图,因此得到 $ ext{n = 5,k = 3}$ 时的解,就可以用那个解 $ ext{+ k}$ 再 $ ext{% n(n = 6)}$ 得到 $ ext{n = 6,k = 3}$ 时的解,如此得到上面的那个递推式也就顺理成章了。


Part4:

但是得到这个递推式后还需要一些修改,因为此题的编号是 $ ext{1 ~ n}$ 而不是 $ ext{0 ~ n - 1}$,杀掉的第一个人是 $m$ 而不是 $k$,具体细节详见代码。


code:

 1 #include <cstdio>
 2 
 3 const int MAXN = 1e4;
 4 
 5 int n, k, m, f[MAXN + 10];
 6 
 7 int main() {
 8     
 9     while(233) {
10         scanf("%d %d %d", &n, &k, &m);
11         if(!n && !k && !m) return 0;
12         f[1] = 0;
13         for(int i = 2; i <= n; ++i) {
14             f[i] = (f[i - 1] + k) % i;
15         }
16         printf("%d
", (((f[n] + (m - k))) % n + n) % n + 1);
17     }
18 
19     return 0;
20 } 
原文地址:https://www.cnblogs.com/qqq1112/p/15100013.html