NoiOpenjudge水题选刷之_搜索

1789:算24

给出4个小于10个正整数,你可以使用加减乘除4种运算以及括号把这4个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24。
这里加减乘除以及括号的运算结果和运算的优先级跟我们平常的定义一致(这里的除法定义是实数除法)。
比如,对于5,5,5,1,我们知道5 * (5 – 1 / 5) = 24,因此可以得到24。又比如,对于1,1,4,2,我们怎么都不能得到24。

第一道水题就看了题解,自己觉得太不好搜了,原来是功力不够;
我们把输入都定义成double,思路是任意两个数之间计算(注意计算方式有六种),把结果存起来,并对其下标打个标记,继续作为一个新的数参与运算,注意回溯。
数据范围非常小,直接暴力即可

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
double eps=1e-6;
double a[10];
bool vis[10];
bool same(double x) {return 24.0-eps<=x && x<=24.0+eps;}
bool dfs(int num)
{
    if (num==4)
    {
        for (int i=1; i<=4; i++)
            if (!vis[i] && same(a[i])) return true;
        return false;
    }
    for (int i=1; i<=4; i++)//枚举第一个数 
        if (!vis[i])
        for (int j=i+1; j<=4; j++)//枚举第二个数 
        if (!vis[j])
        {
            vis[j]=1;
            double tmp1=a[i],tmp2=a[j];
            a[i]=tmp1+tmp2;
            if (dfs(num+1)) return true;
            a[i]=tmp1-tmp2;
            if (dfs(num+1)) return true;
            a[i]=tmp2-tmp1;
            if (dfs(num+1)) return true;
            a[i]=tmp1*tmp2;
            if (dfs(num+1)) return true;
            a[i]=tmp1/tmp2;
            if (dfs(num+1)) return true;
            a[i]=tmp2/tmp1;
            if (dfs(num+1)) return true;
            vis[j]=0;
            a[i]=tmp1;//注意回溯 
        }
    return false;
}

int main()
{
    while (scanf("%lf%lf%lf%lf",&a[1],&a[2],&a[3],&a[4]))
    {
        if (a[1]==0&&a[2]==0&&a[3]==0&&a[4]==0) break;
        memset(vis,0,sizeof(vis));
        if (dfs(1)) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
1792:迷宫

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。如果起点或者终点有一个不能通行(为#),则看成无法办到。

记得这种水题还是刚学bfs时经常做的呢

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int dx[5]={0,-1,0,1}, dy[5]={-1,0,1,0};
int a[110][110];
int bo,k,n,f[110][110],ha,la,hb,lb;
void dfs(int xx,int yy)
{
    if (bo)return;
    if (xx==hb&&yy==lb) {bo=1;return;}
    for(int i=0;i<4;i++)
    {
        int x=xx+dx[i],y=yy+dy[i];
        if (a[x][y]){
            a[x][y]=0;
            dfs(x,y);
        }
    }
}
int main()
{
    cin>>k;
    for (int t=1;t<=k;t++)
    {
        cin>>n;
        memset(a,0,sizeof(a));
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
            {
                char ch;
                cin>>ch;
                if (ch=='.')a[i][j]=1;

            }
        cin>>ha>>la>>hb>>lb;
        ha++;la++;hb++;lb++;
        bo=0;
        if (!a[ha][la]||!a[hb][lb]) {cout<<"NO"<<endl;continue;}
        a[ha][la]=0;
        dfs(ha,la);
        if (bo) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}
1805:碎纸机

1.每次切割之前,先要给定碎纸机一个目标数,而且在每张被送入碎纸机的纸片上也需要包含一个数。
2.碎纸机切出的每个纸片上都包括一个数。
3.要求切出的每个纸片上的数的和要不大于目标数而且与目标数最接近。
举一个例子,如下图,假设目标数是50,输入纸片上的数是12346。碎纸机会把纸片切成4块,分别包含1,2,34和6。这样这些数的和是43 (= 1 + 2 + 34 + 6),这是所有的分割方式中,不超过50,而又最接近50的分割方式。又比如,分割成1,23,4和6是不正确的,因为这样的总和是34 (= 1 + 23 + 4 + 6),比刚才得到的结果43小。分割成12,34和6也是不正确的,因为这时的总和是52 (= 12 + 34 + 6),超过了50。
还有三个特别的规则:
1.如果目标数和输入纸片上的数相同,那么纸片不进行切割。
2.如果不论怎样切割,分割得到的纸片上数的和都大于目标数,那么打印机显示错误信息。
3.如果有多种不同的切割方式可以得到相同的最优结果。那么打印机显示拒绝服务信息。比如,如果目标数是15,输入纸片上的数是111,那么有两种不同的方式可以得到最优解,分别是切割成1和11或者切割成11和1,在这种情况下,打印机会显示拒绝服务信息。
为了设计这样的一个碎纸机,你需要先写一个简单的程序模拟这个打印机的工作。给定两个数,第一个是目标数,第二个是输入纸片上的数,你需要给出碎纸机对纸片的分割方式。

感觉这道搜素题还是比较好的。
自己的方法不是很好,代码很难看,有一些标记,但还是过了

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int n,m,la,lb,a[101],b[101];
int vis[1000000],ans[100];//tong
int nowmax;
struct st{
    int len,a[101];
}s[1001];
int tot,id;
int calc()
{
    int x=n;
    while (x) {a[++la]=x%10; x/=10;}
    x=m;
    while (x) {b[++lb]=x%10; x/=10;}
}
bool check()
{
    int tmp=0;
    for (int i=1; i<=lb; i++) tmp+=b[i];
    if (tmp>n) return true;
    tot++;
    for (int i=1; i<=lb; i++) s[tot].a[i]=b[i];
    vis[tmp]++;
    nowmax=tmp; id=tot; s[tot].len=lb;
    return false;
}

int js(int l,int r)
{
    int ans=0;
    for (int i=r; i>=l; i--)
        ans=(ans<<1)+(ans<<3)+b[i];
    return ans;
}

void doit(int len)
{
    if (ans[len]!=lb) return;
    int tmp=0; tot++;
    for (int i=1; i<=len; i++)
    {
        int tt=js(ans[i-1]+1,ans[i]);
        tmp+=tt;
        s[tot].a[i]=tt;
    }
    if (tmp>n) {tot--; return;}
    vis[tmp]++;
    s[tot].len=len;
    if (tmp>nowmax)
    {
        nowmax=tmp; id=tot;
    }
}

void dfs(int last,int num,int goal)
{
    if (num==goal+1) {doit(goal); return;}
    for (int i=last+1; i<=lb; i++)
    {
        ans[num]=i;
        dfs(i,num+1,goal);
    }
}
void work(int num)
{
    dfs(0,1,num);
}
void csh()
{
    tot=0; id=0; nowmax=0; la=0; lb=0;
    memset(vis,0,sizeof(vis));
}

int main()
{
    while (scanf("%d%d",&n,&m))
    {
        csh();
        if (n==0 && m==0) break;
        if (n==m) {printf("%d %d\n",n,m); continue;}
        calc();
        if (check()) {printf("error\n"); continue;}

        for (int i=lb-1; i>=lb/la-1; i--)
            work(i);
        if (vis[nowmax]>1) {printf("rejected\n"); continue;}
        printf("%d ",nowmax);
        for (int i=s[id].len; i>=1; i--) printf("%d ",s[id].a[i]); printf("\n");
    }
}
1818:红与黑

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

dfs水题,直接计数即可

#include<iostream>
#include<cstring>
using namespace std;
int a[22][22],w,h,total;
void dfs(int x,int y)
{
    total++;
    a[x][y]=0;
    if(a[x+1][y]) dfs(x+1,y);
    if(a[x-1][y]) dfs(x-1,y);
    if(a[x][y+1]) dfs(x,y+1);
    if(a[x][y-1]) dfs(x,y-1);
}
int main()
{
    int x,y;
    cin>>w>>h;char a1;
    while(!(w==0&&h==0))
    {
        total=0;
        memset(a,0,sizeof(a));
        for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++)
        {
            cin>>a1;
            if(a1=='.'||a1=='@')
            a[i][j]=1;
            if(a1=='@')
            {
                x=i;
                y=j;
            }
        }
        dfs(x,y);
        cout<<total<<endl;
        cin>>w>>h;
    }
}
2971:抓住那头牛

农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000)。农夫有两种移动方式:
1、从X移动到X-1或X+1,每次移动花费一分钟
2、从X移动到2*X,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。农夫最少要花多少时间才能抓住牛?

这道水题还做了这么久。。
首先是没有开一个vis数组标记,T的很惨
开了之后vis开小了RE,应该两倍大
然后对于入队条件的判断,是否等于0,最大值的判断
总之,水题不能1A,就是失败

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
int n,m,ans;
bool vis[1000100];
struct st{
    int step,v;
};
queue <st> q;
void bfs()
{
    st a; a.step=0; a.v=n;
    vis[n]=true;
    q.push(a);
    while (!q.empty())
    {
        st now=q.front(); q.pop();
//      printf("%d %d\n",now.step,now.v);
        if (now.v==m) {ans=now.step; return;}
        st tmp;
        tmp.step=now.step+1; tmp.v=now.v+1; 
        if (tmp.v>=0 && !vis[tmp.v])
        {
            q.push(tmp);
            vis[tmp.v]=true;
        }
        tmp.step=now.step+1; tmp.v=now.v-1; 
        if (tmp.v>=0 && !vis[tmp.v])
        {
            q.push(tmp);
            vis[tmp.v]=true;
        }
        tmp.step=now.step+1; tmp.v=now.v*2; 
        if (tmp.v<=100000 && !vis[tmp.v])
        {
            q.push(tmp);
            vis[tmp.v]=true;
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    if (n==m) {printf("0"); return 0;}
    bfs();
    printf("%d",ans);
    return 0;
}
2990:符号三角形

符号三角形的第1行有n个由“+”和”-“组成的符号 ,以后每行符号比上行少1个,2个同号下面是”+“,2个异号下面是”-“ 。计算有多少个不同的符号三角形,使其所含”+“ 和”-“ 的个数相同。
n=7时的1个符号三角形如下:
+ + - + - + +
+ - - - - +
- + + + -
- + + -
- + -
- -
+

这道搜素题还是挺不错的,刚开始没有想出思路。
我们把+-分别看成01,他们之间的操作就是异或
我们同样可以发现,当第一行确定了,这一行就都确定了 (是不是和扫雷有点像)
我们考虑把三角形倒过来,推上一行。注意回溯的过程
输出:c[n];

void dfs(int x) 
{
    if(x>n) return;
    for(int i=0; i<=1; i++) 
    {
        a[1][x]=i;
        tot+=i;
        for(int j=2; j<=x; j++) 
        {
            a[j][x-j+1]=a[j-1][x-j+2]^a[j-1][x-j+1];
            tot+=a[j][x-j+1];
        }
        if(tot*2 == n*(n+1)/2)  c[x]++;
        dfs(x+1);
        tot-=i;
        for(int j=2; j<=x; j++) 
        {
            a[j][x-j+1]=a[j-1][x-j+2]^a[j-1][x-j+1];
            tot-=a[j][x-j+1];
        }
    }
}
666:放苹果

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

用搜索写的话,就是一个枚举组合的东西
用dp写的话,就是一个水水的线性dp
分解成子问题也可以用分治来写

#include<iostream>
using namespace std;
int t,n,m,c;
void search(int k,int f,int t)
{
    if(k==0) {c++;return;}
    if(f==m)
    {
        if(k==0) c++;
        return;
    }
    for(int i=t;i<=k;i++)
    if(k>=i)
    search(k-i,f+1,i);
}
int main()
{
    cin>>t;
    for (int i=1;i<=t;i++)
    {
        c=0;
        cin>>n>>m;
        search(n,0,1);
        cout<<c<<endl;
    }
}

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[1000][1000]={0};//dp[m][n]
int main()
{
    int t,m,n;
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        memset(dp,0,sizeof(dp));
        scanf("%d%d",&m,&n);
        m+=n;
        for(int j=0;j<=m;j++){
            dp[j][j]=1;
            if(j>0)dp[j][1]=1;
        }
        for(int j=1;j<=m;j++){
            for(int k=1;k<=n&&k<=j;k++){
                dp[j][k]=dp[j-1][k-1]+dp[j-k][k];
            }
        }
        printf("%d\n",dp[m][n]);
    }
    return 0;
}

#include<cstdio>
int value(int m,int n)
{
    if(m==0||n==1)
        return 1;
    else
    {
        if(m<n)
            return value(m,m);
        else
            return value(m-n,n)+value(m,n-1);
    }
}
int main()
{
    int t;
    int m,n;
    scanf("%d",&t);
    for(int i=0;i<t;i++)
    {
        scanf("%d%d",&m,&n);
        printf("%d\n",value(m,n));
    }
    return 0;
}
1819:木棒

乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棍的长度都不超过50个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。

这道题还做了好长时间呢
考虑使用二分——二分出一个值,判断是否可以拼凑出来
然而考虑是否具有单调性?即小的不行那么解就一定在大的里面找?
应该不是 况且check的过程非常复杂
然后想到了另一种方法;
答案肯定是在Max–Sum之中的Sum的倍数中产生并且尽量小,所以我们只需要枚举这些数就好了
先从小到大排序,在check的时候贪心选大的接上。
如果能拼出一个原始木棍一定不会对后面的造成影响
但是这样T掉了,查看了题解,采用了题解的更优的搜索方法

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
int cmp(const void*p1,const void*p2)
{
    return *(int*)p2-*(int*)p1;
}
int a[100],v[100];
int n,len,s,tot,min;
bool dfs(int k,int mi,int left)
{
    int i;
    if(left==min) 
        return 1;
    for(i=k;i<=n;i++)
        if(!v[i]&&a[i]<=mi)
        {
            v[i]=1;
            if(a[i]==mi&&dfs(1,len,left-a[i]))
                return 1;
            else if(dfs(i+1,mi-a[i],left-a[i]))
                return 1;
            v[i]=0;
            if(a[i]==min)return 0;
            if(left==tot)return 0;
            if(mi==len)  return 0;
            while(a[i+1]==a[i])
                i++;
        }
    return 0;
}
int main()
{
    int i,res;
    while(scanf("%d",&n),n)
    {
        tot=0;
        for(i=1;i<=n;i++){
            scanf("%d",&a[i]);
            tot+=a[i];
        }
        qsort(a+1,n,sizeof(a[0]),cmp);
        len=a[1];
        res=len;
        memset(v,0,sizeof(v));
        res=tot;
        for(;len<tot;len++)
            if(tot%len==0&&dfs(1,len,tot))
            {
                res=len;
                break;
            }
        printf("%d\n",res);
    }
    return 0;
}
1814:恼人的青蛙

构造出青蛙穿越稻田时的行走路径,并且只关心那些在穿越稻田时至少踩踏了3棵水稻的青蛙。因此,每条青蛙行走路径上至少包括3棵被踩踏的水稻。而在一条青蛙行走路径的直线上,也可能会有些被踩踏的水稻不属于该行走路径
①不是一条行走路径:只有两棵被踩踏的水稻;
②是一条行走路径,但不包括(2,6)上的水道;
③不是一条行走路径:虽然有3棵被踩踏的水稻,但这三棵水稻之间的距离间隔不相等。
请你写一个程序,确定:在一条青蛙行走路径中,最多有多少颗水稻被踩踏。例如,图4的答案是7,因为第6行上全部水稻恰好构成一条青蛙行走路径。

有一种思路枚举任意两个点 O(n^2) 找这条路上的点O(n^3) 然后标记这条路上的所有两个点之间的关系 O(m^2/2)
总的复杂度大概是 O(n^4) 625e12
其实不必On找路径上的点,直接加差即可,原本以为这样复杂度会很高

#include <stdio.h>  
#include <algorithm>  
#include <string.h>  
#include <iostream>  
#include <stdlib.h>  
#include <math.h>  
using namespace std;  
struct bode  
{  
    int x,y;  
} node[5005];  
bool vis[5005][5005];  
int n,disx,disy,r,c,tmpx,tmpy,max1=0,cnt;  
int cmp(const bode &a,const bode &b)  
{  
    if(a.y==b.y)return a.x<b.x;  
    else return a.y<b.y;  
}  
int main()  
{  
    scanf("%d%d",&r,&c);  
    scanf("%d",&n);  
    memset(vis,false,sizeof(vis));  
    for(int i=0; i<n; i++)  
    {  
        scanf("%d%d",&node[i].x,&node[i].y);  
        vis[node[i].x][node[i].y]=true;  
    }  
    sort(node,node+n,cmp);  
    max1=0;  
    for(int i=0;i<n;i++)  
    {  
        for(int j=i+1;j<n;j++)  
        {  
            disx=node[j].x-node[i].x;  
            disy=node[j].y-node[i].y;  
            if(node[i].x-disx>0&&node[i].x-disx<=r&&node[i].y-disy>0&&node[i].y-disy<=c)continue;  
            tmpx=node[j].x+disx;  
            tmpy=node[j].y+disy;  
            cnt=0;  
            while(tmpx>0&&tmpx<=r&&tmpy>0&&tmpy<=c)  
            {  
                if(vis[tmpx][tmpy])cnt++;  
                else  
                {  
                    cnt=0;  
                    break;  
                }  
                tmpx+=disx;  
                tmpy+=disy;  
            }  
            max1=max(max1,cnt);  
        }  
    }  
    if(max1)  
        printf("%d\n",max1+2);  
    else puts("0");  
    return 0;  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值