2021年11.13周总结

这周继续刷搜索的题,从刷题中知道了一些之前不知道的技巧与思想

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没打,失去一场锻炼的机会。以后有比赛还是需要注意下的。这周末刷题了继续更新

上一篇:用for; while...do; do...while; 写出九九乘法表


下一篇:LeetCode 47 全排列II(有重复元素 dfs)