题解 P6183 【[USACO10MAR]The Rock Game S】

题意

求一个长度为 nnOX 串的全排列,并要求:

1.1. 第一个排列必须全是 O

2.2. 除了最后一个排列外,每一个排列都不能重复。

3.3. 相邻的排列只能有一个位置不一样。

4.4. 遍历完所有排列后,还要能回到第一个排列。

分析

首先,看到数据范围 (1n15)(1≤n≤15),可以一眼看穿这就是个深搜,对于当前这个状态,不断修改他的每一位,标记为走过,再递归下去,因为第一个全是 O 的状态出现了两次,所以我们走到第一个状态时不需标记。然后可以发现,每个状态的每个位置都是由 OX 组成,这不正是二进制吗?所以我们可以用一个整数来表示当前的状态,这个整数二进制状态下的每一位就代表一个 OX,如9(1001)9(1001)代表 XOOX。接着,我们再考虑深搜时剪枝,如果这个状态前面已经有过了,就剪枝。

最后,我们得出了正确做法:

step 1:step\ 1: 深搜 step 23step\ 2\sim3

step 2:step\ 2: 判断边界,如果每个状态都走了一遍,并回到起点,就输出,终止程序。

step 3:step\ 3: 不断选择更改目前这个状态的每一位,如果这个状态没有走过,就标记这个状态,并存到答案的数组(记录每一步走的是哪个状态)中,把这个状态带到step 1step\ 1递归下去,然后别忘了回溯。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=(1<<15)+1;//最多有2^15种状态 
int n,m,a[N];//n如题意,m为状态总数,a数组存储答案的每个状态 
bool b,c[N]={1};//b用于终止程序,c数组存储每个状态是否走过,c[0]提前标记,不然n=2,3会错
void pr()//输出 
{
	for(int i=0;i<n;i++)cout<<"O";cout<<endl;//第一个没有标记的状态要另外输出 
	for(int i=m-1;i>=0;i--)
	{
		for(int j=n-1;j>=0;j--)
		if((a[i]>>j)&1)printf("X");
		else printf("O");
		printf("\n");
	}
}
void dfs(int w)//深搜,w表示已经有了几个状态 
{
	if(b==1)return;//如果已经输出了就终止其他的dfs 
	if(w==m)//如果走完了所有状态 
	{
		if(a[m]==0)//如果最后又回到开始状态 
		{
			pr();//输出 
		    b=1;//标记为已经输出 
		}
		return ;
	}
	int v;//v表示目前状态改变后的状态的值 
	for(int i=n-1;i>=0;i--)
	{
		if((a[w-1]>>i)&1)v=a[w-1]-(1<<i);//把O改成X或把X改成O 
		else v=a[w-1]+(1<<i);
		if(!c[v])//如果修改后没有访问过 
		{
			c[v]=1;a[w]=v;//标记并存入答案 
			dfs(w+1);//搜索下去 
			c[v]=0;a[w]=0;//回溯千万别忘了 
		}
	}
}
int main()
{
    cin>>n;
    m=(1<<n);//总共有2^n种状态(不算第一种) 
    dfs(1);//第一种状态不搜索,因为已经确定,也不标记为走过 
	return 0;
}
posted @ 2021-02-18 11:22  luckydrawbox  阅读(12)  评论(0)    收藏  举报  来源