【C++】题解:P1259 黑白棋子的移动_递归+模拟_算法竞赛_洛谷

P1259 黑白棋子的移动 题解

Link:Luogu - P1259

题目描述

2 n 2n 2n 个棋子排成一行,开始为位置白子全部在左边,黑子全部在右边,如下图为 n = 5 n=5 n=5 的情况:

移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如 n = 5 n=5 n=5 时,成为:

任务:编程打印出移动过程。

输入格式

一个整数 n n n

输出格式

若干行,表示初始状态和每次移动的状态,用 o \verb!o! o 表示白子, * \verb!*! * 表示黑子, - \verb!-! - 表示空行。

样例 #1

样例输入 #1

7

样例输出 #1

ooooooo*******--
oooooo--******o*
oooooo******--o*
ooooo--*****o*o*
ooooo*****--o*o*
oooo--****o*o*o*
oooo****--o*o*o*
ooo--***o*o*o*o*
ooo*o**--*o*o*o*
o--*o**oo*o*o*o*
o*o*o*--o*o*o*o*
--o*o*o*o*o*o*o*

提示

4 ≤ n ≤ 100 4\leq n\leq 100 4n100


解题思路

提示:这里先看懂思路就行,不用细究下标什么的问题。看代码时再看。

这道题一看就是一个细节模拟题。

可以直接找规律,以样例为例,从第 2 行开始,每两行为一组。
每一组操作如下:

  1. 先把 -- 移到还未修改序列的中间(把一对 o* 放到最右边);
  2. 再把 -- 移到右边已经摆好的 o* 左侧,让未修改序列中间的 o*凑到一块。

然后,不难看出序列长度一定是偶数,且每次进行的操作都是一样的,符合递归结构。可以从右往左递归模拟这个过程。

但这样到最后会出问题,因为后面的一些字符不符合上述规律……???
在这里插入图片描述


请大家肃静!(敲锤子)

我们发现这也是一个分治, n n n 经过第一次处理后,剩下的 [ 1 , ( 2 n + 2 ) − 2 ] [1,(2n + 2) - 2] [1,(2n+2)2] 区间里的字符就可以看作是 n − 1 n-1 n1 的答案。

然后,题目给的 n n n 下界为 4 4 4,所以当 n = 4 n=4 n=4之后的操作就不符合上面规律了。

但又发现 n = 4 n=4 n=4时前面的答案固定,只有后面多出来一些 o*o*......

所以直接在代码中用一个 t t t 记录现在的 n n n到多少了,每次递归 t − 1 t-1 t1。如果 t = 4 t=4 t=4 直接输出固定答案返回即可。

声明:这看似是投机取巧,实则是游戏的必然规律。请不要误解。

AC Code

提示:代码里的许多下标操作都很复杂,干看肯定看不明白。一定一定在草稿纸上模拟一遍,你就会恍然大悟。所以,这种模拟题一定要自己写一遍才有感觉(

#include <bits/stdc++.h>
using namespace std;

int n;
char s[210]; // 注意空间要开到2*n+2以上

/*
@param l,r 当前操作区间为[l,r],提示左右端点
@param cnt 表示当前剩余黑棋子的个数(白棋子个数与其相等)
@param t   当前分治的“n”是多少
*/
void work(int l, int r, int cnt, int t){ 
	if(t == 4){ // 终止条件
		cout << "ooo--***o*"; for(int i = 1; i <= n-4; i ++){cout << "o*";} cout << endl;
		cout << "ooo*o**--*"; for(int i = 1; i <= n-4; i ++){cout << "o*";} cout << endl;
		cout << "o--*o**oo*"; for(int i = 1; i <= n-4; i ++){cout << "o*";} cout << endl;
		cout << "o*o*o*--o*"; for(int i = 1; i <= n-4; i ++){cout << "o*";} cout << endl;
		cout << "--o*o*o*o*"; for(int i = 1; i <= n-4; i ++){cout << "o*";} cout << endl; 
		return ;
	}
	
	// 1.把--移到待修改序列的中间,把一对o*放到最后
	int mid = (r - 2) / 2; // 中间靠左字符的为止,-2是把最右边的--减去
	s[mid] = s[mid + 1] = '-';
	s[r] = '*', s[r - 1] = 'o';
	cout << (s + 1) << endl;
	
	// 中间处理
	cnt --; // 上一步移动完,就新摆好了一粒棋子,所以要-1
	r -= 2; // 摆好的两个棋子就不算在区间里了
	
	// 2.把--移到右边已经摆好的o*左侧,让未修改序列中间的o和*凑到一块
	// 本质是:把[r-(cnt-1),r]的所有字符左移两位,然后把--放到r和r-1
	for(int i = r-(cnt-1); i <= r; i ++) s[i - 2] = s[i];
	s[r] = s[r - 1] = '-';
	cout << (s + 1) << endl;
	
	// 3.继续递归
	work(1, r, cnt, t - 1);
}

void solve()
{
	cin >> n;
	// 构造原始字符串
	for(int i = 1; i <= n; i ++) s[i] = 'o';
	for(int i = 1; i <= n; i ++) s[n + i] = '*';
	s[2*n + 1] = s[2*n + 2] = '-';
	// 先输出一次原串
	cout << (s + 1) << '\n';
	// 递归求解
	work(1, 2*n + 2, n, n);
}

signed main()
{
	ios :: sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	solve();
	return 0;
}

End

模拟是算法竞赛的基础能力。希望大家能把模拟和找规律的能力都练扎实了,这样才能在赛时发挥出稳定的成绩。

让我们一起进步吧,拜拜ヾ(•ω•`)o

推销个人洛谷博客洛谷主页

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值