这周继续刷搜索的题,从刷题中知道了一些之前不知道的技巧与思想
P1120 小木棍
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 5050。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
思路:一看是求最小长度,就想的是用队列的广度优先搜索,然后就wr了,看了题解,知道了这是一道DFS,这个题需要一个一个数开始遍历,直到找到适合的长度。所以在进行搜索时就需要大量的剪枝了。
剪枝1:所得长度必须可以整除这些小木棍的长度
剪枝2:用了当前木棍后无法拼好 但是剩下的木棍长度还等于当前木棍长度 跳出
剪枝3:首先:能运行到这表明最大的a[i]也不能满足条件 然后:如果len==pp 即一组新的木棍 那么以后的也不能满足条件
剪枝4:当前长度不行 其他长度也不行
然后就出现了一个87分,就差一组样例就过的代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=0x3f3f3f;
bool cmp(int x,int y)
{
return x>y;
}
int n,t,sum=0,cnt=0,a[70],res,vis[70],pp;
int dfs(int len,int sta,int now)
{
if(now==res)
return 1;
if(len==0)
if(dfs(pp,1,now+1))
return 1;
for(int j=sta;j<=cnt;j++)
if(!vis[j]&&a[j]<=len)
{
vis[j]=1; // 保证点未被用过
if(dfs(len-a[j],j+1,now)) // 搜下一个点
return 1;
vis[j]=0;
if(len==a[j]||len==pp)
break;
while(a[j+1]==a[j])
j++;
}
return 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
if(t<=50)
{
sum+=t;
a[++cnt]=t;
}
}
sort(a+1,a+1+cnt,cmp);
for(int i=a[1];i<=sum;i++)
if(sum%i==0)
{
pp=i; // 全局记录长度
res=sum/i;
if(dfs(i,1,0))
{
printf("%d",i);
return 0;
}
}
return 0;
}
再看题解,还是细节中把握的不好
正确代码:
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[70],vis[70];
int sum=0,maxa=-1,aver=0,cnt=0;
int n,flag=0,m=0;
bool cmp(int x,int y){
return x>y;//逆序
}
void dfs(int step,int num,int now){//从now开始,而不是每次从1开始,节省时间
//step表示搜索的组数,num表示每一组积累的长度,now表示现在正遍历的点
// printf("%d %d\n",step,num);
if(step==cnt||flag){
printf("%d\n",aver),exit(0);//退出
flag=1;
return;
}
if(num==aver){
dfs(step+1,0,1);//从头开始
return;
}
if(aver-num<a[n]) return;//这步是说明在前面的两步判断之后,如果我们的平均值减去这个num的值
//比整个序列的最小值还小,那一定不能再凑成新的一组了。
for(int i=now;i<=n;i++){
if(!vis[i]&& num+a[i]<=aver){
//除了保证i没有被访问过,还要保证num+当前的a[i]小于平均值,才能进入下一步的dfs
vis[i]=1;
dfs(step,num+a[i],i+1);//开始下一步
vis[i]=0; //回溯过程的处理非常关键
if(num+a[i]==aver)
return;//回溯找到一组,不可行
if(num==0)
return;//回溯到上一层最开始的地方,不可行
while(a[i]==a[i+1])
i++;//回溯时消除重复的值 (1)
}
}
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x>50){
continue;
}
a[++m]=x;
if(a[m]>maxa) maxa=a[m];//最大值
sum+=a[m];
}
n=m;
sort(a+1,a+n+1,cmp);//逆序排列,从最大值往回找
for(int i=maxa;i<=sum;i++){
if(sum%i==0){
aver=i,cnt=sum/i;
dfs(0,0,1);
if(flag) break;
}
}
printf("%d\n",aver);
return 0;
}
P1126 机器人搬重物
机器人移动学会(RMI)现在正尝试用机器人搬运物品。机器人的形状是一个直径1.61.6米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 N \times MN×M 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:向前移动11步(Creep);向前移动2步(Walk);向前移动33 步(Run);向左转(Left);向右转(Right)。每个指令所需要的时间为11 秒。请你计算一下机器人完成任务所需的最少时间。
思路:这种题我上周做了很多了,思路都一样,最少时间,利用BFS,起点进队列,然后弹出,然后放入左转,右转,向前1,2,3。直到终点,若队列为空了还没到终点,那么输出-1(走不到)
我一开始用的一个三位数组vis[n][n][n]来表示,i行j列f方向已经走过,就是剪枝,然后死活过不了,就看的题解,题解中,它用了一个fun(a,b,c)函数来给这个状态赋一个唯一的值。这样做好像也可以的
我的那个vis数组的代码给覆盖了没有了,看不对就没提交,只能直接放fun函数的正解了
#include<cstdio>
#include<cstdlib>
#include<queue>
#define For(a,b,c,d) for(register int a=b;a<=c;a+=d)
const int my[ 4 ] = { 0 , 1 , 0 , -1 } , mx[ 4 ] = { -1 , 0 , 1 , 0 } ; //每个方向的移动规则
int n , m , maze[ 60 ][ 60 ] ;
bool vis[ 20000 ] ; //记录访问过的位置
inline int fun( int a , int b , int c ) { return c * 2700 + a * 51 + b ;} //返回每种情况唯一对应的key值
struct pos { //每个位置的信息
int x , y ; //坐标
int f ; //方向
int mov ; //已移动时间
} ;
std::queue<pos> que ;
inline bool zq( int x , int y ) { //判断是否撞上障碍物
if( maze[ x ][ y ] || maze[ x + 1 ][ y ] || maze[ x ][ y + 1 ] || maze[ x + 1 ][ y + 1 ] )
return 1 ;
return 0 ;
}
inline void bfs() {
int x , y , tx , ty , f , d , mov , lx , ly ;
char c ;
scanf("%d %d %d %d %c" , &x , &y , &tx , &ty , &c );
switch( c ) {
case 'N': f = 0 ;
break ;
case 'E': f = 1 ;
break ;
case 'S': f = 2 ;
break ;
case 'W': f = 3 ;
break ;
}
pos temp ;
temp.x = x , temp.y = y , temp.f = f , temp.mov = 0 ;
que.push( temp ) ;
while( !que.empty() ) {
temp = que.front() ;
que.pop() ;
x = temp.x , y = temp.y , f = temp.f , d = fun( x , y , f ) , mov = temp.mov ;
if( x == tx && y == ty ) { //判断是否是重点
printf("%d",mov) ;
exit( 0 ) ;
}
if( vis[ d ] ) //判断是否被访问过
continue ;
vis[ d ] = 1 ;
temp.mov ++ ;
temp.f = ( f + 4 - 1 ) % 4 ; //左转
que.push( temp ) ;
temp.f = ( f + 4 + 1 ) % 4 ; //右转
que.push( temp ) ;
temp.f = f ;
For( i , 1 , 3 , 1 ) { //向前移动
lx = x + mx[ f ] * i , ly = y + my[ f ] * i ;
if( lx <= 0 || ly <= 0 || lx >= n || ly >= m || zq( lx , ly ) )
break ;
temp.x = lx ;
temp.y = ly ;
que.push( temp ) ;
}
}
printf("-1");
}
int main() { //主函数
scanf("%d %d" , &n , &m ) ;
For( i , 1 , n , 1 ) {
For( j , 1 , m , 1 ) {
scanf("%d", &maze[ i ][ j ] );
}
}
bfs() ;
return 0;
}
P1189 SEARCH
年轻的拉尔夫开玩笑地从一个小镇上偷走了一辆车,但他没想到的是那辆车属于警察局,并且车上装有用于发射车子移动路线的装置。
那个装置太旧了,以至于只能发射关于那辆车的移动路线的方向信息。
编写程序,通过使用一张小镇的地图帮助警察局找到那辆车。程序必须能表示出该车最终所有可能的位置。
小镇的地图是矩形的,上面的符号用来标明哪儿可以行车哪儿不行。“.”表示小镇上那块地方是可以行车的,而符号“X”表示此处不能行车。拉尔夫所开小车的初始位置用字符的“*”表示,且汽车能从初始位置通过。
汽车能向四个方向移动:向北(向上),向南(向下),向西(向左),向东(向右)。
拉尔夫所开小车的行动路线是通过一组给定的方向来描述的。在每个给定的方向,拉尔夫驾驶小车通过小镇上一个或更多的可行车地点。
思路:题做多了就是思路好想,这种题和上题其实没什么区别,首先,我把每次向某个方向的位置都标记,然后依次遍历,但是每次在走一个新的方向时记得memset一下vis数组,新的一轮开始了。还有一点,起点不能是最后的终点。
代码:
#include<bits/stdc++.h>
using namespace std;
char a[60][60];
int n,m;
int vis[60][60];
int ans=0x3f3f3f;
int dis[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
int pos[60][60];
int cnt[60][60];
void search(int d){
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(!vis[i][j]&&cnt[i][j]){
int dx=i+dis[d][0];
int dy=j+dis[d][1];
cnt[i][j]=0;
while(pos[dx][dy]){
cnt[dx][dy]=1;
vis[dx][dy]=1;
dx=dx+dis[d][0];
dy=dy+dis[d][1];
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>a[i][j];
if(a[i][j]=='*')
pos[i][j]=1,cnt[i][j]=1;
else if(a[i][j]=='.')
pos[i][j]=1;
else
pos[i][j]=0;
}
int k;
cin>>k;
string str;
for(int i=0;i<k;i++){
cin>>str;
if(str[0]=='N')
search(0);
else if(str[0]=='E')
search(1);
else if(str[0]=='S')
search(2);
else
search(3);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
if(cnt[i][j]==1)
cout<<"*";
else{
if(a[i][j]=='*')
cout<<".";
else
cout<<a[i][j];
}
cout<<endl;
}
return 0;
}
这周有场div2没打,失去一场锻炼的机会。以后有比赛还是需要注意下的。这周末刷题了继续更新