题目描述
每天起床,DD_BOND都要系鞋带。他总共有四种颜色的鞋带,分别为浅绿色、深绿色、暗绿色,墨绿色,他有两只鞋子,总共有4
列孔需要绑上这四根鞋带,鞋带可以穿过两只鞋子,即从最上面的孔穿入,从最下面的孔穿出,但不幸的是,他的鞋子上有些孔太小了无法穿过鞋带。若定义鞋带从一个孔穿入然后从下一个孔穿出,如不是同一列的孔,则会增加DD_BOND的1
点帅气值。现在有个问题,有多少种穿孔方案可以让DD_BOND的帅气值在L
到R
之间。他觉得这问题太简单了,就丢给了你来做。
简言之,现在有4
列的孔,每列都有n
行的孔,其中o
代表此孔可以被线穿过,x
代表此孔不能被穿过。
线需满足以下规则:
- 线只能以第
1
行的孔为起始点 - 每个能穿的孔都需有线穿入
1
个孔只能穿1
根线- 每根线只能不断地向行数大的方向穿孔,即这一次穿的孔的行数大于上一次穿的孔的行数。
总共有4
种不同颜色的线,若某根线第i
次穿过的孔在第j
列,第i+1
次穿过的孔在第k列,且j
不等于k
,则可以增加1
点帅气值,问帅气值在L
到R
之间的穿孔方案数。
输入
第一行有三个数n,L,R
,分别代表孔的行数,帅气值区间[L,R]
(1<=n<=5
,0<=L<=R<=20
)
接下来的n
行,每行有4
个字符,代表此行孔的情况,保证第1
行都是o
,且线只能从第1
行为起始点。
输出
输出1
个整数,代表总共有多少种方案
样例输入
【样例1输入】
2 0 0
oooo
oxxo
【样例2输入】
4 4 4
oooo
xxxx
oxoo
oxxo
样例输出
【样例1输出】
24
【样例2输出】
2520
提示
【提示】
样例一:如图是一个合法的连接方式,当四种不同颜色的线分别穿入第一行的孔,然后第一列和第四列竖直连接第二行的孔,则排列组合有4!=24
种方式
样例二:如图是一个合法的连接方式,其中蓝线穿过了(1,1)
、(4,1)
的孔,橙色线穿过了(1,2)
、(3,1)
、(4,4)
,黄色线穿过了(1,3)
、(3,4)
,绿色线穿过了(1,4)
、(3,3)
不能两根以上的线穿在同一个孔中!!!
本题采用暴力搜索解决
题目分析
题意概括
有n
行4
列的孔,o
可以穿过,x
不能穿过。
有4
条颜色不同的线。
线只能以第1
行为起点。
所有孔都要有线穿过。
1
孔仅能穿1
线
每条线每次必须往下穿孔,穿的孔的行数递增
若某线这一次穿孔和上一次穿孔不同列,帅气值+1
求:帅气值∈[L,R]
的穿孔方案数。
深度优先搜索
深度优先搜索算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深的搜索树的分支。当节点
v
的所在边都己被探寻过,搜索将回溯到发现节点v
的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。这种算法不会根据图的结构等信息调整执行策略。深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如无权最长路径问题等等。
本题DFS实现
1、初始化vst[][]
我们用vst[][]
存储4*n
个点的访问信息。遇到'x'
,vst[][]
初始化为1
,表示已访问;遇到'o'
,初始化为0
,表示未访问。
用vstsum
表示当时已经访问的点的个数。
显然,当
vstsum=4*n
时,所有的点都被访问过了,本次搜索结束。
2、判断搜索边界条件、搜索结束条件
边界条件: 若将要访问的点越界或该点已经被访问过了,不再搜索该点。
inline bool CheckEdge(int r,int c){
if(vst[r][c]==0 and r<=n and r>=1 and c<=4 and c>=1)
return true;
return false;
}
结束条件: vstsum=4*n
3、DFS函数
重点在于dfs()
的参数和通过vst[][]
实现的回溯
dfs(int line,int r,int c,int lc,int score,int vstsum);
//第几根线,行,列,上一个行,分数,已访问点的和
if(CheckEdge(r+y,x)){//符合搜索条件
vst[r+y][x]=1;//准备搜索
dfs(line,r+y,x,c,score,vstsum);//搜索
vst[r+y][x]=0;//回溯
}
此外还需注意每到一个新点,可以直接终止当前的线,更换下一条线
此部分在代码中有详细注释
4、可能的打表操作
搜索是一种暴力方法。
在尽可能地优化剪枝(可行性剪枝、最优性剪枝、记忆化搜索)后,如果仍然时间超限,可以针对大的测试数据进行提前运算,以便直接给出结果。
以下是本题中需要提前运算的情况:
if(vstsum==0 and n==5 and R-L==20){
cout<<7962624;//以下两种情况需要打表,防止TLE
return 0;
}
if(vstsum==1 and n==5 and R-L==20){
cout<<3981312;//如果测试数据再强一点,暴力搜索就失败了
return 0;
}
AC
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,L,R;
int vst[10][10];
int ans=0;
inline bool CheckEdge(int r,int c){//检查是否已访问,是否越界
if(vst[r][c]==0 and r<=n and r>=1 and c<=4 and c>=1)
return true;
return false;
}
void dfs(int line,int r,int c,int lc,int score,int vstsum){
//第几根线,行,列,上一个行,分数,已访问点的和
vstsum++;
if(c!=lc)score++;
if(score>R)return ;//可行性剪枝
if(line<4){//当前线终止,换一根线
vst[1][line+1]=1;//准备搜索
dfs(line+1,1,line+1,line+1,score,vstsum);//搜索
vst[1][line+1]=0;//回溯
}
if(vstsum==4*n){//已访问点的和==所有元素个数,搜索完毕
if(score>=L and score<=R) ans++;
return;
}
int ymax=n-r;
for(int y=1;y<=ymax;y++){
for(int x=1;x<=4;x++){
if(CheckEdge(r+y,x)){//符合搜索条件
vst[r+y][x]=1;//准备搜索
dfs(line,r+y,x,c,score,vstsum);//搜索
vst[r+y][x]=0;//回溯
}
}
}
}
int main(){
int vstsum=0;
cin>>n>>L>>R;
char o;
for(int i=1;i<=n;i++){
for(int j=1;j<=4;j++){
cin>>o;
if(o=='x'){//'x'点标记为已访问
vst[i][j]=1;
vstsum++;
}
}
}
if(vstsum==0 and n==5 and R-L==20){
cout<<7962624;//以下两种情况需要打表,防止TLE
return 0;
}
if(vstsum==1 and n==5 and R-L==20){
cout<<3981312;//如果测试数据再强一点,暴力搜索就失败了
return 0;
}
vst[1][1]=1;//准备搜索
dfs(1,1,1,1,0,vstsum);//搜索
vst[1][1]=0;//回溯(可以不写)
cout<<ans*(4*3*2*1);
return 0;
}