试题链接:POJ 1012 Joseph
一、使用循环链表进行模拟(超时)
首先创建一个长度为 2 k 2k 2k的首尾相接的循环链表,每个结点从 1 1 1到 2 k 2k 2k进行标号。对于每个满足 k + 1 ≤ m % 2 k ≤ 2 k k+1 \le m\%2k \le 2k k+1≤m%2k≤2k的 m m m,从 1 1 1开始,每数到第 m m m号,检查他是否为坏人(即此人的编号 k + 1 ≤ n ≤ 2 k k+1 \le n \le 2k k+1≤n≤2k),若是则删除之;否则表明这个 m m m的取值不对,尝试下一个 m m m。
使用循环链表的超时代码:
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int number;
Node *next;
};
// create a cyclic list of 2k length, numbered from 1 to 2k
void create(Node *&head, Node *&tail, int k)
{
Node *previous, *current;
head = (Node *)malloc(sizeof(Node));
head->number = 1;
head->next = head;
tail = head;
previous = head;
for (int i = 2; i <= 2*k; ++i)
{
current = (Node *) malloc(sizeof(Node));
current->number = i;
current->next = head;
previous->next = current;
previous = current;
tail = current;
}
}
// free list
void destroy(Node *&head, Node *&tail)
{
Node *previous = tail, *current = head, *temp;
while (current->next != current)
{
temp = current;
previous->next = current->next;
current = current->next;
free(temp);
}
free(current);
head = tail = NULL;
}
// kill m-th people
// 1..k is good, k+1..2k is bad
bool search(Node *&head, Node *&tail, int m, int k)
{
Node *current = head, *previous = tail, *temp;
int cnt = 0;
int dead = 0; // number of dead people
while (current->next != current)
{
++cnt;
if (cnt == m)
{
if (1 <= current->number && current->number <= k) // kill good guy
return false;
if (current == head)
head = head->next;
if (current == tail)
tail = previous;
temp = current;
previous->next = current->next;
current = current->next;
free(temp);
cnt = 0;
++dead;
if (dead == k) // all bad guys are killed
return true;
}
else
{
previous = current;
current = current->next;
}
}
return false;
}
int main()
{
int k;
while (scanf("%d", &k) && k > 0)
{
bool found = false;
for (int i = 1; !found; ++i)
{
for (int m = i*k+1; m <= i*k+k; ++m)
{
Node *head = NULL, *tail = NULL;
create(head, tail, k);
if (search(head, tail, m, k))
{
printf("%d\n", m);
found = true;
break;
}
destroy(head, tail);
}
}
}
return 0;
}
二、利用递推公式求解
设现有 n n n个人围成一圈,从1到 n n n编号,每轮报到 m m m的人被枪决,记 f ( n , m ) f(n, m) f(n,m)为此轮被枪决的人的编号。
可以这样考虑,当有 n n n个人时,第 m m m个人被枪决(先简单考虑 m ≤ n m \le n m≤n的情形),则下一轮只有 n − 1 n-1 n−1个人时,从第 m + 1 m+1 m+1号人开始报数,那么我们可以将第 m + 1 m+1 m+1号人编号为 1 1 1,这样就从规模为 n n n的问题变成了 n − 1 n-1 n−1的问题。原问题和子问题下的人员编号如下表所示:
( n , k ) (n, k) (n,k)问题 | ( n − 1 , k ) (n-1, k) (n−1,k)问题 |
---|---|
1 1 1 | n − m + 1 n-m+1 n−m+1 |
2 2 2 | n − m + 2 n-m+2 n−m+2 |
3 3 3 | n − m + 3 n-m+3 n−m+3 |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ |
m − 1 m-1 m−1 | n − 1 n-1 n−1 |
m m m | killed |
m + 1 m+1 m+1 | 1 1 1(start next) |
m + 2 m+2 m+2 | 2 2 2 |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ |
n − 1 n-1 n−1 | n − 1 − m n-1-m n−1−m |
n n n | n − m n-m n−m |
但是这样我们是无法推出 f ( n , m ) f(n, m) f(n,m)和 f ( n − 1 , m ) f(n-1, m) f(n−1,m)之间的关系式的。仔细琢磨一些题目,编号 1 1 1— k k k的为好人,编号 ( k + 1 ) (k+1) (k+1)— 2 k 2k 2k的为坏人,而且我们要找到合适的 m m m使得前 k k k轮被杀的都是坏人(即 k + 1 ≤ f ( i , m ) ≤ 2 k , 2 k ≥ i ≥ k + 1 k+1 \le f(i, m) \le 2k, 2k \ge i \ge k+1 k+1≤f(i,m)≤2k,2k≥i≥k+1)。因此我们可以这样思考:无需纠结于好人与坏人编号的精确值,只要保证前 k k k人是好人即可,即每一轮杀掉一个坏人后将其后的所有坏人编号全部减一(如果好人不被杀,好人的编号永远不会变)。
例如,对于
k
=
3
,
m
=
5
k = 3, m = 5
k=3,m=5,开始时,大家的编号如下:
1 2 3 4 5 6
第一轮从1号报数,杀掉5号,则其后的6号先前填充,编号变为:
1 2 3 4 5
第二轮从5号(实际上是最初的6号)报数,杀掉4号,则其后的5号先前填充,编号变为:
1 2 3 4
第三轮从4号(实际上是最初的6号)报数,杀掉4号,剩下的都是好人,所以这个
m
m
m的取值是正确的。在整个过程中,好人1、2、3的编号从未变化。
那么,在这种条件下,原问题和子问题下的人员编号如下表所示:
( n , k ) (n, k) (n,k)问题 | ( n − 1 , k ) (n-1, k) (n−1,k)问题 |
---|---|
1 1 1 | 1 1 1 |
2 2 2 | 2 2 2 |
3 3 3 | 3 3 3 |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ |
m − 1 m-1 m−1 | m − 1 m-1 m−1 |
m m m | killed |
m + 1 m+1 m+1 | m m m |
m + 2 m+2 m+2 | m + 1 m+1 m+1(start next) |
⋮ \vdots ⋮ | ⋮ \vdots ⋮ |
n − 1 n-1 n−1 | n − 2 n-2 n−2 |
n n n | n − 1 n-1 n−1 |
我们可以推出
f
(
i
−
1
,
m
)
=
(
f
(
i
,
m
)
−
2
+
m
)
%
(
i
−
1
)
+
1
f
(
n
,
m
)
=
(
m
−
1
)
%
n
+
1
\begin{aligned} f(i-1, m) &= (f(i, m)-2+m) \% (i-1) + 1 \\ f(n, m) &= (m-1) \% n + 1 \end{aligned}
f(i−1,m)f(n,m)=(f(i,m)−2+m)%(i−1)+1=(m−1)%n+1
我们只要一一尝试 { m ∣ i k + 1 ≤ m ≤ i k + k , i = 1 , 2 , ⋯ } \{ m | ik+1 \le m \le ik+k, i=1, 2, \cdots \} {m∣ik+1≤m≤ik+k,i=1,2,⋯}中的取值,找到使得 k + 1 ≤ f ( i , m ) ≤ 2 k , 2 k ≥ i ≥ k + 1 k+1 \le f(i, m) \le 2k, 2k \ge i \ge k+1 k+1≤f(i,m)≤2k,2k≥i≥k+1得以满足的最小的 m m m。
代码如下(为了避免超时,这里打表记录结果):
#include <stdio.h>
int result[15] = { 0 };
bool check(int m, int k)
{
int killed = (m - 1) % (2*k) + 1;
if (killed <= k)
return false;
for (int i = 2*k-1; i > k; --i)
{
killed = (killed + m - 2) % i + 1;
if (killed <= k)
return false;
}
return true;
}
int main()
{
int k;
bool found;
while (scanf("%d", &k) && k > 0)
{
if (result[k])
{
printf("%d\n", result[k]);
continue;
}
found = false;
for (int i = 1; !found; ++i)
{
for (int m = i*k+1; m <= i*k+k; ++m)
{
if (check(m, k))
{
printf("%d\n", m);
result[k] = m;
found = true;
break;
}
}
}
}
return 0;
}
打表的结果为:
result[1] = 2;
result[2] = 7;
result[3] = 5;
result[4] = 30;
result[5] = 169;
result[6] = 441;
result[7] = 1872;
result[8] = 7632;
result[9] = 1740;
result[10] = 93313;
result[11] = 459901;
result[12] = 1358657;
result[13] = 2504881;
result[14] = 13482720;