HDU 1997、2184、2175、2511 汉诺塔VII、VIII、IX、X

这4个题目的联系实在太紧密了,以至于,代码好多可以重用

题目:

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
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;
}


题目:

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
#include
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
#include
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的讲解,还有下面的题目)


题目:

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个盘子。

有个约瑟夫问题和这个差不多点击打开我的博客

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

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

代码:

#include
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;
}


题目:

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
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;
}

你可能感兴趣的:(HDU 1997、2184、2175、2511 汉诺塔VII、VIII、IX、X)