Joseph
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 1652 Accepted Submission(s): 1031
Problem Description
The Joseph's problem is notoriously known. For those who are not familiar with the original problem: from among n people, numbered 1, 2, . . ., n, standing in circle every mth is going to be executed and only the life of the last remaining person will be saved. Joseph was smart enough to choose the position of the last remaining person, thus saving his life to give us the message about the incident. For example when n = 6 and m = 5 then the people will be executed in the order 5, 4, 6, 2, 3 and 1 will be saved.
Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy.
Input
The input file consists of separate lines containing k. The last line in the input file contains 0. You can suppose that 0 < k < 14.
Output
The output file will consist of separate lines containing m corresponding to k in the input file.
Sample Input
3 4 0
Sample Output
5 30
思路:
如果用c++STL的list做这题的话,会超时,目前没有想到怎么用list在AC这题,这里用的方法是模拟一个链表的过程,思路简单来说就是逐个枚举 m 的值,直到满足条件就输出 m ,但是如果用list的话,每删除一个list元素,就要整个list遍历一遍,一般上了千次的遍历,耗时都会很大,所以,不超时的方法就是跳过遍历,直接通过找出+ m 之后的元素,然后删除,每枚举一个 m 值得时候,就只需要遍历 k个元素就行了,时间会快很多;还有另外一个要注意的地方,以后出现类似的题要有习惯性的添加一个打表 的数组,避免判定机多次输入时间超时,下面给出代码(代码下面会给出模拟的方法)
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<time.h>
#include<cmath>
#include<stack>
#include<list>
#include<queue>
#include<map>
#define E exp(1)
#define PI acos(-1)
#define mod (ll)(1e9+9)
#define INF 0x3f3f3f3f;
//#define _CRT_SECURE_NO_WARNINGS
//#define LOCAL
using namespace std;
typedef long long ll;
typedef long double lb;
int main(void)
{
#ifdef LOCAL
freopen("data.in.txt", "r", stdin);
freopen("data.out.txt", "w", stdout);
#endif
//上面的内容可以忽视,是做题备用的代码
int k,tp, cnt, num, flag, table[15];
memset(table, 0, sizeof(table));
while (scanf("%d", &k) != EOF) {
if (k == 0) break;
if (table[k] != 0) { //打表的数组,就是把求出的答案保存下来,
printf("%d\n", table[k]); //判定机测下一多组数据,如果是求过的数据
continue; //的话可以直接输出
}
int m = k + 1, cur;
// 这里并没用到什么跟链表有关的内容,只是在for循环的尾部,
//加了一个 if(到最尾数值) 让 i = 0 重新在遍历一遍,
//这样就能像遍历链表一样了
while (1) {
num = 2 * k ; flag = 0; cur = 1;
while (num > k) {
// 这个tp的值是当前遍历到的值 cur 继续报数的次数,
//其实 tp换成m是没有什么问题的,
//只是,m的值一旦大起来,就会很耗时
//比如说 m = 10 要在一个有6个元素的数组报数,
//报10次和报4次的结果是一样的,
tp = m % num == 0 ? num : m % num;
//这里的 下一个cur的值是通过两种情况求的, 第一种是报了tp次数后
//没有超过目前有的元素的个数,就可以直接 +tp
// 如果超过了,就通过 互补的关系求得 下一个cur
if (cur + tp - 1 > num) {
cur = tp - (num - cur)-1; //互补关系求的cur
}
else cur = cur + tp - 1;
if (cur <= k) {
flag = 1;
break;
}
else num--;
}
if (flag) {
m++;
//这里是枚举m的时候的一个剪枝代码,如果第一次报数的结果
//小于或等于K的值,那这个数就不必要去试了,直接把目前枚举的值
//加到大于k的值然后去试
if (m % (2 * k) <= k && m % (2 * k) != 0) {
m += (k - m % (2 * k) + 1);
}
}
else{
table[k] = m;
printf("%d\n", m);
break;
}
}
}
//system("pause");
return 0;
}
这里模拟的过程大概是这样的:
当 m = 5 , k = 3 的时候
1 2 3 4 5 6 报数5 之后,按照链表的手法是删除5这个元素,但这里并不是,而是把 5 这个位置让给 6 来做
结果是这样的:
1 2 3 4 5
那么目前元素有5个,根据代码里的方法求 tp 然后从 5 这个元素开始报数, cur将会是 4 这个元素,那么 4 这个元素的位置就让 5 (就是要删除的这个元素的上一个)来做,删除的过程很简单,就是在遍历的前设置一个 num = 2*k; 只要num--就能不用继续遍历大于num的数组的数字;
然后结果是 1 2 3 4 ,继续报数,最后得到cur =3,这个时候 num==k 那么就可以退出循环了;