一、算法分析
暴力和枚举几乎是一个同义词,看到数据范围,n最大只有10,这就几乎等于直接告诉你要进行枚举了。紫书上有一道例题。
提供了生成排列的思路。枚举出所有到达牛的顺序,这样对于每次移动,我们只需要关心当前牛和下一头牛之间的关系(如果不剪枝的话)再在必要的地方进行剪枝(剪枝显然是要剪的)。
通常解题时,如果正着做不好做,就反着做。如果条件不够,就发掘隐含条件。如果情境复杂,就建模转化。如果计算复杂,就分类计算。如果规模复杂,可以考虑分治。如果不好入手,可以先构造再判断。如果不好构造,可以暴力枚举所有情况再判断。
本题可以采用枚举,将“解答题”转化为“判断题”。判断时的方案如下:
要求所有路径都必须是(正东南西北),我们可以认为东南西北是一个xOy坐标系,然后将两点坐标相减,得到一个方向向量,再将这个向量分别向x轴和y轴进行映射,也就是相当于将其移到原点再正交分解,同时转化为单位向量。如果两次映射的结果均不等于0,则说明这个移动不是正向的,需要抛弃。
二、代码及注释
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=15;
int n;
int ans;
int posx[maxn];
int posy[maxn]; //各个点的x和y坐标,读入了之后就不要再变动了
int pl[maxn]; //排列的可能,直接用next_permutation
void meijv(){
int lst_dx=0; //上一个方向向量
int lst_dy=0;
for(int i=-1;i<n;i++){
int x,y; //当前点
int dx,dy; //方向向量
int xp,yp; //下一个点
if(i==-1){ //起点
x=0;
y=0;
}
else{
x=posx[pl[i]];
y=posy[pl[i]];
}
if(i==n-1){ //最后一个点
xp=0;
yp=0;
}
else{
xp=posx[pl[i+1]];
yp=posy[pl[i+1]];
}
dx=xp-x;
if(dx!=0) dx/=abs(dx);
dy=yp-y;
if(dy!=0) dy/=abs(dy);
if(dx && dy) return; //如果不走四个正向
if(i==-1){
lst_dx=dx;
lst_dy=dy;
continue;
}
if(dx==lst_dx && dy==lst_dy) return;
lst_dx=dx;
lst_dy=dy;
}
ans++;
}
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>posx[i]>>posy[i];
for(int i=0;i<n;i++) pl[i]=i; //起始排列
do{ //枚举所有可能,依次进行检查
meijv();
}while(next_permutation(pl,pl+n)); //next_permutation自带返回值,生成完所有排列后跳出
cout<<ans;
return 0;
}