发布于 

约瑟夫问题

约瑟夫问题

用线段树历经千辛万苦终于做出来了,(;´༎ຶД༎ຶ )。结果上网一找貌似有个\(O(n)\)的递推写法

线段树

题目来源:Code[VS]

题目是经典的约瑟夫问题,但是数据比原题大了许多,若直接模拟的话一定会超时,所以我们需要用其他方法优化。

我们知道,模拟的时间复杂度为$ O(nm) $,第一个nn次游戏,没有优化的空间,所以我们要从m下手。m应为从当前结点向右找到第m个结点,对于每个结点我们都需判断其是否被取过,所以较慢。所以我们可以记录一下某个区间内还存在多少个结点,因为游戏的过程中还要更改结点,所以用线段树再合适不过了。

于是我们建立一棵树,将其分为以下几个模块:

1
2
3
4
5
6
7
8
9
struct SegmentTree{
trueint a[MAXN]; //记录当前结点代表的数(若非叶节点则为0)
int num[MAXN]; //记录当前结点下有几个数

truevoid Build(); //建树
truevoid Del(); //删除某一结点
truevoid Update(); //一次游戏过程
truevoid Main(); //主程序的运行
};

其中,Build(),Del()Main()都较易实现,唯一需要慎重考虑的即为Update()函数。

以下,我们对Upate()所需实现的功能从普遍到特殊进行简单的分析(设我们剩余需跳过的长度为last):

  1. Update(i,m)表示从i开始走m步(不包括i).
  2. (刚进入Update(i,m)时)当前为一个普通的叶结点时,我们需要向父节点搜素,搜至当前 结点i使得num[i]为小于m的最大值.
  3. 当前结点已搜至跟结点时,我们只需执行Update(0,last%num[1]),等价于已经多次跑遍整个环。
  4. 当前结点不为叶结点时,若右兄弟存在,则跳至右兄弟,否则跳至父节点的右兄弟,以此类推.
  5. 若当前结点及其父节点等不存在右兄弟(即它们一直都是右子树),则Upate(0,last),即结束此圈。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <cstdio>
#include <algorithm>

using namespace std;

struct SegmentTree
{
trueint a[5000000];
trueint num[5000000]; //记录当前结点

truevoid Build(int i,int n,int l) //建树
{
truetruenum[i] = n;
truetrueif (n==1)
truetrue{
truetruetruea[i] = l;
truetruetruereturn;
truetrue}

truetruea[i] = 0;

truetrueBuild(i*2,n/2,l);
truetrueBuild(i*2+1,(n+1)/2,l+n/2);
true}

truebool Check(int i)
{
truetrueint x = i;
truetruewhile (x%2==1 && x!=1)
truetruetruex /= 2;
truetruereturn x==1?true:false;
true}

truevoid Del(int x) //删除当前结点
{
truetruefor (int i = x; i>=1; i/=2)
truetruetruenum[i] --;
true}

trueint Update(int x,int m) //继续报数
{
truetrueint last = m;
truetrueint i;

truetrueif (x==0)
truetruetruefor (x=1; !a[x]; x*=2); //x为0时,先向左下搜到子叶

truetrue//只有当1.当前节点不为1;2.当前结点为左子树;3.当前结点为右子树且其左兄弟的Num为0 时可以向上搜素
truetruefor (i=x; num[i/2]<=m && i!=1 &&(i%2==0 || num[i]==num[i/2]); i/=2); //向上找到能跳过的最多节点的区间或找到根节点

truetrue//printf("\n!%d %d\n",i,m);

truetruewhile (1) //如果没有搜至子叶
truetrue{
truetruetrue//printf("--%d %d %d\n",i,num[i],last);

truetruetrueif (num[i]>last)
truetruetruetruei = i*2;
truetruetrueelse if (num[i]==last){ //如果正好相等,则直接找到当前区间的最后一个数
truetruetruetrue//printf("[1*]%d %d \n",i,last);
truetruetruetruewhile (!a[i])
truetruetruetruetruei = num[i*2+1]?i*2+1:i*2; //找到存在的最后面的子叶
truetruetruetruereturn i;
truetruetrue}else{
truetruetruetruelast -= num[i]; //跳过当前区间
truetruetruetrue//printf("[2*]%d %d \n",i,last);
truetruetruetrueif (Check(i)) return Update(0,last); //如果当前结点及其父节点不存在右兄弟
truetruetruetrueif (i%2==0) i++; //跳至右兄弟
truetruetruetrueelse i = i/2 + 1; //或跳至父节点的右兄弟
truetruetrue}
truetrue}

truetruereturn i;
true}

truevoid Main(int n,int m) //主函数
{
truetrueBuild(1,n,1);
true// int x = Build(1,n,1,m); //获取第一个要删除的数
true// Del(x);
true// printf("%d ",a[x]);
/*
for (int i =1 ; i<=3*n; i++)
printf("%d ",a[i]);
printf("\n");

for (int i =1 ; i<=3*n; i++)
printf("%d ",num[i]);
printf("\n");
*/
truetrueint x = 0;
truetruefor (int i =1; i<=n; i++) //循环删除剩下的数
truetrue{
truetruetruex = Update(x,m);
truetruetrueDel(x);
truetruetrueprintf("%d ",a[x]);
truetrue}
true}
}Tree;

int main()
{
trueint n,m;
truescanf("%d%d",&n,&m);

trueTree.Main(n,m);

}

递归解法

链接


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 [@Songer](https://blog.songer.xyz/) 创建,使用 Stellar 作为主题。