汉诺塔

本文探讨了不同版本的汉诺塔问题,包括经典三柱汉诺塔、四柱汉诺塔、汉诺塔III、IV、V、VI、VII和VIII,以及它们的解题策略和递推公式。通过分析每个变体的特点,提出了求解移动次数的算法,如利用优先级、递推关系和特定操作顺序。这些变体涉及递归、位运算和矩阵运算,展示了汉诺塔问题在数学和计算机科学中的丰富应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

HYSBZ 1019 汉诺塔

HDU 1207 汉诺塔II(四柱汉诺塔)

HDU 2064 汉诺塔III

HDU 2077 汉诺塔IV

HDU 1995 汉诺塔V

HDU 1996 汉诺塔VI

HDU 1997 汉诺塔VII

HDU 2184 汉诺塔VIII

HDU 2175 汉诺塔IX

HDU 2511 汉诺塔X


HYSBZ 1019 汉诺塔

题目:

 汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成。一开始n个盘子都摞在柱子A上,
大的在下面,小的在上面,形成了一个塔状的锥形体。

 对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移
动的盘子一定放在比它更大的盘子上面(如果移动到空柱子上就不需要满足这个要求)。我们可以用两个字母来描
述一次操作:第一个字母代表起始柱子,第二个字母代表目标柱子。例如,AB就是把柱子A最上面的那个盘子移到
柱子B。汉诺塔的游戏目标是将所有的盘子从柱子A移动到柱子B或柱子C上面。有一种非常简洁而经典的策略可以帮
助我们完成这个游戏。首先,在任何操作执行之前,我们以任意的次序为六种操作(AB、AC、BA、BC、CA和CB)
赋予不同的优先级,然后,我们总是选择符合以下两个条件的操作来移动盘子,直到所有的盘子都从柱子A移动到
另一根柱子:(1)这种操作是所有合法操作中优先级最高的;(2)这种操作所要移动的盘子不是上一次操作所移
动的那个盘子。可以证明,上述策略一定能完成汉诺塔游戏。现在你的任务就是假设给定了每种操作的优先级,计
算按照上述策略操作汉诺塔移动所需要的步骤数。

Input

  输入有两行。第一行为一个整数n(1≤n≤30),代表盘子的个数。第二行是一串大写的ABC字符,代表六种操
作的优先级,靠前的操作具有较高的优先级。每种操作都由一个空格隔开。

Output

  只需输出一个数,这个数表示移动的次数。我们保证答案不会超过10的18次方。

Sample Input 
3
AB BC CA BA CB AC

Sample Output 
7

根据最小的盘子的移动方式分类,其实本质上也就只有3种情况

其中一种情况是最普通的汉诺塔的情况,根据递推式可以算出结果是2^n-1

另外2种情况也是差不多,结果分别是3^(n-1)和3^(n-1)*2-1

代码:

#include<iostream>
#include<string>
#define ll long long
using namespace std;
 
int pri[6];//AB BC CA BA AC CB 的优先级
 
int id(char e, char f)
{
	if (e == 'A')if (f == 'B')return 0; else return 4;
	if (e == 'B')if (f == 'C')return 1; else return 3;
	if (f == 'A')return 2; else return 5;
}
 
int getpri(string s)
{
	return pri[id(s[0], s[1])];
}
 
ll f1(int n)//3^(n-1)
{
	ll ans = 1;
	while (--n)ans *= 3;
	return ans;
}
ll f2(int n)//3^(n-1)*2-1
{
	return f1(n) * 2 - 1;
}
ll f3(int n)//2^n-1
{
	ll ans = 1;
	while (n--)ans *= 2;
	return ans-1;
}
ll ans(int n)
{
	if (getpri("AB") > getpri("AC"))
	{
		if (getpri("BA") > getpri("BC"))return f2(n);
		if (getpri("CA") > getpri("CB"))return f3(n);
		return f1(n);
	}
	if (getpri("CA") > getpri("CB"))return f2(n);
	if (getpri("BA") > getpri("BC"))return f3(n);
	return f1(n);
}
 
int main()
{
	int n;
	cin >> n;
	string s;
	for (int i = 0; i < 6; i++)
	{
		cin >> s;
		pri[id(s[0], s[1])] = 6-i;//记录AB BC CA BA AC CB 的优先级
	}
	cout << ans(n);
	return 0;
}

HDU 1207 汉诺塔II(四柱汉诺塔)

题目:

Description

经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。Gardon就收到了一个汉诺塔玩具作为生日礼物。 
  Gardon是个怕麻烦的人(恩,就是爱偷懒的人),很显然将64个圆盘逐一搬动直到所有的盘子都到达第三个柱子上很困难,所以Gardon决定作个小弊,他又找来了一根一模一样的柱子,通过这个柱子来更快的把所有的盘子移到第三个柱子上。下面的问题就是:当Gardon在一次游戏中使用了N个盘子时,他需要多少次移动才能把他们都移到第三个柱子上?很显然,在没有第四个柱子时,问题的解是2^N-1,但现在有了这个柱子的帮助,又该是多少呢? 

Input

包含多组数据,每个数据一行,是盘子的数目N(1<=N<=64)。 

Output

对于每组数据,输出一个数,到达目标需要的最少的移动数。 

Sample Input

1
3
12

Sample Output

1
5
81

这个问题我有些想法,但我不确定。

后来我上网搜了一下,发现网上有篇博客,里面的操作方法和递推式和我自己想的一模一样。

据说这个叫Frame–Stewart算法,还据说这个目前都没有被证明正确性。

里面有张图片写了递推式(有一点点错误被我改过来了)

我自己求的递推式也是这样的,但是我不是很确定这是正确的。

代码:

#include<iostream>
using namespace std;


int main()
{
	/*long long list[65];
	long long a = 1;
	list[0] = 0;
	for (int i = 1; i <= 64; i++)
	{
		list[i] = 2 << 20;
		for (int j = 0; j < i; j++)
		{
			if (i - j > 20)continue;
			if (list[i] > list[j] * 2 + (a << (i - j)) - 1)list[i] = list[j] * 2 + (a << (i - j)) - 1;
		}
		cout << list[i] << ",";
	}*/
	int list[65] = { -1, 1, 3, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 129, 161, 193, 225, 257, 289, 321, 385, 449, 513, 577, 641, 705, 769, 897, 1025, 1153, 1281, 1409, 1537, 1665, 1793, 2049, 2305, 2561, 2817, 3073, 3329, 3585, 3841, 4097, 4609, 5121, 5633, 6145, 6657, 7169, 7681, 8193, 8705, 9217, 10241, 11265, 12289, 13313, 14337, 15361, 16385, 17409, 18433 };
	int n;
	while (cin >> n)cout << list[n] << endl;
	return 0;
}

HDU 2064 汉诺塔III

题目:

Description

约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。 
现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到小盘的上面。 
Daisy已经做过原来的汉诺塔问题和汉诺塔II,但碰到这个问题时,她想了很久都不能解决,现在请你帮助她。现在有N个圆盘,她至少多少次移动才能把这些圆盘从最左边移到最右边? 

Input

包含多组数据,每次输入一个N值(1<=N=35)。

Output

对于每组数据,输出移动最小的次数。

Sample Input

1
3
12

Sample Output

2
26
531440

这个题目的递推式比较简单,f(n)=3f(n-1)+2

可以直接求出通项公式,f(n)=3^n-1,也可以用数组记录所有的答案。

代码:

#include<iostream>
using namespace std;

int main()
{
	/*long long a = 0;
	for (int i = 1; i <= 35; i++)
	{
		a = a * 3 + 2;
		cout << a << ",";
	}*/
	long long l[35] = { 2, 8, 26, 80, 242, 728, 2186, 6560, 19682, 59048, 177146, 531440, 1594322, 4782968, 14348906, 43046720, 129140162, 387420488, 1162261466, 3486784400, 10460353202, 31381059608, 94143178826, 282429536480, 847288609442, 2541865828328, 7625597484986, 22876792454960, 68630377364882, 205891132094648, 617673396283946, 1853020188851840, 5559060566555522, 16677181699666568, 50031545098999706 };
	int n;
	while (cin >> n)cout << l[n - 1] << endl;
	return 0;
}

HDU 2077 汉诺塔IV

题目:

Description

还记得汉诺塔III吗?他的规则是这样的:不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到小盘的上面。xhd在想如果我们允许最大的盘子放到最上面会怎么样呢?(只允许最大的放在最上面)当然最后需要的结果是盘子从小到大排在最右边。 

Input

输入数据的第一行是一个数据T,表示有T组数据。 
每组数据有一个正整数n(1 <= n <= 20),表示有n个盘子。 

Output

对于每组输入数据,最少需要的摆放次数。 

Sample Input

2
1
10

Sample Output

2
19684

这个题目就稍微有些技巧了。

首先考虑,在汉诺塔III中,把n个盘子全部移到中间需要多少步。

其实如果仔细的思考的话,可以发现,在汉诺塔III中,要把n个盘子全部移到右边,其实就是分2大步。

第一步,全部移到中间,第二步,全部移到右边。

而且显然他们的步数相等,所以都是f(n)=3^n-1的一半。

回到本题,整个过程分为3个部分。

一,把n-1个盘子移到中间,(3^(n-1)-1)/2步

二,把最大的盘子移到右边,2步。

三,把n-1个盘子移到右边,(3^(n-1)-1)/2步

一共3^(n-1)+1步。

代码:

#include<iostream>
using namespace std;

int main()
{
	/*long long a = 2;
	for (int i = 1; i <= 20; i++)
	{
		cout << a << ",";
		a = a * 3 - 2;
	}*/
	int l[20] = { 2, 4, 10, 28, 82, 244, 730, 2188, 6562, 19684, 59050, 177148, 531442, 1594324, 4782970, 14348908, 43046722, 129140164, 387420490, 1162261468 };
	int t, n;
	cin >> t;
	while (t--)
	{
		cin >> n;
		cout << l[n - 1] << endl;
	}
	return 0;
}

HDU 1995 汉诺塔V

题目:

Description

用1,2,...,n表示n个盘子,称为1号盘,2号盘,...。号数大盘子就大。经典的汉诺塔问 
题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于 
印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小 
顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱 
子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。我们 
知道最少需要移动2^64-1次.在移动过程中发现,有的圆盘移动次数多,有的少 。 告之盘 
子总数和盘号,计算该盘子的移动次数.

Input

包含多组数据,首先输入T,表示有T组数据.每个数据一行,是盘子的数目N(1<=N<=60)和盘 
号k(1<=k<=N)。 

Output

对于每组数据,输出一个数,到达目标时k号盘需要的最少移动数。 

Sample Input

2
60 1
3 1

Sample Output

576460752303423488
4

首先,问题的答案肯定是关于n-k的一个函数。

于是接下来我们只需要考虑k=1的情况。

规律实在太明显了,我就不扯什么原理了。

答案是2^(n-k)

代码:

#include<iostream>
using namespace std;

int main()
{
	int t, n, k;
	long long a = 1;
	cin >> t;
	while (t--)
	{
		cin >> n >> k;
		cout << (a << (n - k)) << endl;
	}
	return 0;
}

注意,1<<(n-k)会溢出,所以需要long long的常数

HDU 1996 汉诺塔VI

题目:

Description

n个盘子的汉诺塔问题的最少移动次数是2^n-1,即在移动过程中会产生2^n个系列。由于 
发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱 
子从下往上的大小仍保持如下关系 : 
n=m+p+q 
a1>a2>...>am 
b1>b2>...>bp 
c1>c2>...>cq 
计算所有会产生的系列总数. 

Input

包含多组数据,首先输入T,表示有T组数据.每个数据一行,是盘子的数 
目N<30.

Output

对于每组数据,输出移动过程中所有会产生的系列总数。

Sample Input

3
1
3 
29

Sample Output

3
27
68630377364883

代码:

#include<iostream>
using namespace std;

int main()
{
	/*long long a = 3;
	for (int i = 1; i < 30; i++)
	{
		cout << a << ",";
		a = a * 3;
	}*/
	long long l[29] = { 3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049, 177147, 531441, 1594323, 4782969, 14348907, 43046721, 129140163, 387420489, 1162261467, 3486784401, 10460353203, 31381059609, 94143178827, 282429536481, 847288609443, 2541865828329, 7625597484987, 22876792454961, 68630377364883 };
	int t, n;
	cin >> t;
	while (t--)
	{
		cin >> n;
		cout << l[n - 1] << endl;
	}
	return 0;
}

HDU 1997 汉诺塔VII

题目:

Description

n个盘子的汉诺塔问题的最少移动次数是2^n-1,即在移动过程中会产生2^n个系列。由于发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱子从下往上的大小仍保持如下关系 : 
n=m+p+q 
a1>a2>...>am 
b1>b2>...>bp 
c1>c2>...>cq 
ai是A柱上的盘的盘号系列,bi是B柱上的盘的盘号系列, ci是C柱上的盘的盘号系列,最初目标是将A柱上的n个盘子移到C盘. 给出1个系列,判断它是否是在正确的移动中产生的系列. 
例1:n=3 



是正确的 
例2:n=3 



是不正确的。 
注:对于例2如果目标是将A柱上的n个盘子移到B盘. 则是正确的.

Input

包含多组数据,首先输入T,表示有T组数据.每组数据4行,第1行N是盘子的数目N<=64. 
后3行如下 
m a1 a2 ...am 
p b1 b2 ...bp 
q c1 c2 ...cq 
N=m+p+q,0<=m<=N,0<=p<=N,0<=q<=N,

Output

对于每组数据,判断它是否是在正确的移动中产生的系列.正确输出true,否则false 

Sample Input

6
3
1 3
1 2
1 1
3
1 3
1 1
1 2
6
3 6 5 4
1 1
2 3 2
6
3 6 5 4
2 3 2
1 1
3
1 3
1 2
1 1
20
2 20 17
2 19 18
16 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

Sample Output

true
false
false
false
true
true

因为我是先做了汉诺塔VIII然后才做的汉诺塔VII(本题),所以是直接用的汉诺塔VIII的结论做的。

其实就是求是否存在这样的一个m,使得m次移动之后的状态就是题目所给的状态。

如果我输出的是true,其实这个时候的m的值表示的刚好就是,m次移动之后的状态就刚好满足。

那么m到底怎么求呢?显然和搜索无关。

其实就是n个方程,汉诺塔VIII里面是m已知,那么n个方程依次可以求出每个盘子在哪。

反过来,就是关于m的一元n次方程组,问是否有解。

如果把m看成x1+2 * x2+4 * x3+8 * x4。。。其中xi是0或者1,即m的每一位,那么该方程就是n元n次方程组。

而且本题不需要求解方程组的方法,只需要依次求出各个xi即可(或者判定为无解)

相当于系数矩阵为下三角矩阵的n元n次方程组,只要每次都把前面已经求出来的解代入,即可求出1解,直到全部求出。

代码:

#include<iostream>
using namespace std;

int list[65];

int main()
{
	int t, n, l, a, f;
	long long m;
	cin >> t;
	while (t--)
	{
		cin >> n;
		for (int i = 0; i < 3; i++)
		{
			cin >> l;
			while (l--)
			{
				cin >> a;
				list[a] = i;
			}
		}
		m = 0;
		bool flag = false;
		for (int i = n; i>0; i--)
		{
			m *= 2;
			f = ((i % 2) * 2 - 1)*((n % 2) * 2 - 1)*list[i];
			if ((m - f) % 3 == 0)continue;
			else if (((m - f) % 3 + 2) % 3 == 0)m += 1;
			else
			{
				flag = true;
				break;
			}
		}
		if (flag)cout << "false" << endl;
		else cout << "true" << endl;
	}
	return 0;
}

HDU 2184 汉诺塔VIII

题目:

Description

1,2,...,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.在第1根柱子上的盘子是a[1],a[2],...,a[n]. a[1]=n,a[2]=n-1,...,a[n]=1.即a[1]是最下面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.问移动m次后的状况. 

Input

第1行是整数T,表示有T组数据,下面有T行 
每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1 

Output

输出移动m次后的状况.每组输出3行,第i行第1个数是第i根柱子上的盘子数,然后是盘子的号数. 

Sample Input

3
3 2
4 5
39 183251937942

Sample Output

1 3
1 2
1 1
2 4 1
1 3
1 2
13 39 36 33 30 27 24 21 18 15 12 9 4 1
12 38 35 32 29 26 23 20 17 14 11 8 5
14 37 34 31 28 25 22 19 16 13 10 7 6 3 2

代码:

//我惊奇的发现其实我的输出结果,连n=4,m=5的时候都不对
//当n为偶数的时候,后面2行我输出的是反的
#include<iostream>
#include<stack>
using namespace std;
 
stack<int>s[3];
 
int main()
{
	int t;
	cin >> t;
	long long n, g, p, f;
	long long m;
	long long a = 1;
	while (t--)
	{
		cin >> n >> m;
		for (int i = 0; i < 3; i++)while (!s[i].empty())s[i].pop();
		for (int i = 1; i <= n; i++)
		{
			g = (m >> (i + 1)) % 3;
			f = (i % 2) * 2 - 1;
		p = (f*(g - ((m&(a << i))>0) - ((m&(a << (i - 1)))>0) + 3) % 3 + 3) % 3;
			s[p].push(i);
		}
		for (int i = 0; i < 3; i++)
		{
			cout << s[i].size();
			while (!s[i].empty())
			{
				cout << " " << s[i].top();
				s[i].pop();
			}
			cout << endl;
		}
	}
	return 0;
}

吓得我赶紧重写重提交,又AC了一次。顺便把m改成了每次都除2。

代码:

#include<iostream>
#include<stack>
using namespace std;
 
stack<int>s[3];
 
int main()
{
	int t;
	cin >> t;
	long long n, p, f;
	long long m;
	while (t--)
	{
		cin >> n >> m;
		for (int i = 0; i < 3; i++)while (!s[i].empty())s[i].pop();
		for (int i = 1; i <= n; i++)
		{
			f = ((i % 2) * 2 - 1)*((n % 2) * 2 - 1);	//移动的方向
			p = (f*(m + m % 2) % 3 + 3) % 3;	//移动的距离
			m /= 2;
			s[p].push(i);
		}
		for (int i = 0; i < 3; i++)
		{
			cout << s[i].size();
			while (!s[i].empty())
			{
				cout << " " << s[i].top();
				s[i].pop();
			}
			cout << endl;
		}
	}
	return 0;
}

原理就不解释了,可以把m化成二进制观察规律。(关于m的运算可以看看上面汉诺塔VII的讲解,还有下面的题目)

HDU 2175 汉诺塔IX

题目:

Description

1,2,...,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上. 
在第1根柱子上的盘子是a[1],a[2],...,a[n]. a[1]=n,a[2]=n-1,...,a[n]=1.即a[1]是最下 
面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上. 
问第m次移动的是那一个盘子.

Input

每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1.n=m=0退出

Output

输出第m次移动的盘子的号数.

Sample Input

63 1
63 2
0 0

Sample Output

1
2

这个题目的规律描述起来比较简单。假设m的最后一个1在从右往左第i位,那么第i次操作就是移动第i个盘子。

有个约瑟夫问题和这个差不多 POJ - 3372 Candy Distribution 约瑟夫问题_nameofcsdn的博客-优快云博客_约瑟夫问题

关于约瑟夫问题和汉诺塔问题的紧密联系,别人都做了很多研究,我就不扯了,反正都是二进制就对了。

其实上面的汉诺塔VIII是用到了这个结论的,不过我没有明说出来。

代码:

#include<iostream>
using namespace std;


int main()
{
	int n;
	long long m;
	while (cin >> n >> m)
	{
		if (n == 0)break;
		int i = 1;
		while (m % 2 == 0)
		{
			i++;
			m /= 2;
		}
		cout << i << endl;
	}
	return 0;
}

HDU 2511 汉诺塔X

题目:

Description

1,2,...,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.在第1根柱子上的盘子是a[1],a[2],...,a[n]. a[1]=n,a[2]=n-1,...,a[n]=1.即a[1]是最下面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.问第m次移动的是哪一个盘子,从哪根柱子移到哪根柱子.例如:n=3,m=2. 回答是 :2 1 2,即移动的是2号盘,从第1根柱子移动到第2根柱子 。 

Input

第1行是整数T,表示有T组数据,下面有T行,每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1 

Output

输出第m次移动的盘子号数和柱子的号数. 

Sample Input

4
3 2
4 5
39 183251937942
63 3074457345618258570

Sample Output

2 1 2
1 3 1
2 2 3
2 2 3

这个题目,真的是。。。把上面2个题目的代码杂交,得到的就是正确的代码

代码:

#include<iostream>
using namespace std;


int main()
{
	int t, n;
	long long m;
	cin >> t;
	while (t--)
	{
		cin >> n >> m;
		if (n == 0)break;
		int i = 1;
		while (m % 2 == 0)
		{
			i++;
			m /= 2;
		}
		cout << i << " ";
		int f = ((i % 2) * 2 - 1)*((n % 2) * 2 - 1);	//移动的方向
		cout << (f*((m - 1) + (m - 1) % 2) % 3 + 3) % 3 + 1 << " " << (f*(m + m % 2) % 3 + 3) % 3 + 1 << endl;		
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值