文章目录
- P1259 黑白棋子的移动 题解
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路
- AC Code
- End
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 4≤n≤100
解题思路
提示:这里先看懂思路就行,不用细究下标什么的问题。看代码时再看。
这道题一看就是一个细节模拟题。
可以直接找规律,以样例为例,从第 2 行开始,每两行为一组。
每一组操作如下:
- 先把
--
移到还未修改序列的中间(把一对o*
放到最右边); - 再把
--
移到右边已经摆好的o*
左侧,让未修改序列中间的o
和*
凑到一块。
然后,不难看出序列长度一定是偶数,且每次进行的操作都是一样的,符合递归结构。可以从右往左递归模拟这个过程。
但这样到最后会出问题,因为后面的一些字符不符合上述规律……???
请大家肃静!(敲锤子)
我们发现这也是一个分治, n n n 经过第一次处理后,剩下的 [ 1 , ( 2 n + 2 ) − 2 ] [1,(2n + 2) - 2] [1,(2n+2)−2] 区间里的字符就可以看作是 n − 1 n-1 n−1 的答案。
然后,题目给的 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 t−1。如果 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
推销个人洛谷博客、洛谷主页。