2021/09/08_基础搜索例题练习(1)

2021/09/13_基础搜索例题练习(1)

2021/09/13 A.M. 7:40-11:00

Problems Save Cell Volume Decompose Form Arrange Egypt Gold Cake
预估分数 100 100 100 100 100 100 100 100  100
实际分数 10 100 100 100 100 18 100 100  80

解题报告

 

A:Save_营救

题目描述
    铁塔尼号遇险了!他发出了求救信号。距离最近的哥伦比亚号收到了讯息,时间就是生命,必须尽快赶到那里。
    通过侦测,哥伦比亚号获取了一张海洋图。这张图将海洋部分分化成 n*n 个比较小的单位,其中用 1 标明的是陆地,用 0 标明是海洋。船只能从一个格子,移到相邻的四个格子。
    为了尽快赶到出事地点,哥伦比亚号最少需要走多远的距离。

输入
    第一行为 n,下面是一个 n*n 的 01 矩阵,表示海洋地图。
    最后一行为四个小于 n 的整数,分别表示哥伦比亚号和铁塔尼号的位置。

输出
    哥伦比亚号到铁塔尼号的最短距离,答案精确到整数。

样例输入
3
001
101
100
1 1 3 3

样例输出
4

数据范围

N<=1000
题面

  蠢蠢的一道宽搜板题。

  但是蒟蒻我还是没a,气愤之下发现自己没有判断边界。

#include<queue>
#include<cstdio>
#include<iostream>

struct node{
    int x,y;
    int sum;
};

std::queue<node>q;

int n;
bool m[1010][1010];
int x1,y1,x2,y2;

int dx[]={-1,0,1,0},
    dy[]={0,1,0,-1};

void Bfs(){
    node str;
    str.x=x1,str.y=y1,str.sum=0;
    q.push(str);
    while(1){
        node frm;
        frm=q.front();
        q.pop();
        for(int i=0;i<=3;i++){
            int xx=frm.x+dx[i],yy=frm.y+dy[i];
            if(xx==0||yy==0||xx>n||yy>n)continue;
            if(m[xx][yy])continue;
            if(xx==x2&&yy==y2){
                printf("%d",frm.sum+1);
                exit(0);
            }
            m[xx][yy]=1;
            node to;
            to.x=xx,to.y=yy,to.sum=frm.sum+1;
            q.push(to);
        }
    }
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        char S[1010];
        scanf("%s",S);
        for(int j=0;j<n;j++)
            m[i][j+1]=(S[j]-'0'==1);
    }
    scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
    if(x1==x2&&y1==y2)return(printf("0")&&0);
    Bfs();
}
View Code

 

B:Cell_细胞

题目描述
    一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。

输入
    第一行两个整数,之间用一个空格隔开,表示矩阵的行数m和列数n。
    第二行到第m+1行为mxn数字矩阵,数字之间没有空格。

输出
    一行一个整数,表示细胞个数。

样例输入
4 10
0234500067
1034560500
2045600671
0000000089

样例输出
4
题面

  也是一道初学宽搜的板题。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>

struct node{
    int x,y;
};

std::queue<node>q;

int m,n,ans;
bool mp[1010][1010];

int dx[]={-1,0,1,0},
    dy[]={0,1,0,-1};

void Bfs(int x0,int y0){
    node sta;
    sta.x=x0,sta.y=y0;
    q.push(sta);
    while(!q.empty()){
        node frm;
        frm=q.front();
        q.pop();
        for(int i=0;i<=3;i++){
            int xx=frm.x+dx[i],yy=frm.y+dy[i];
            if(!mp[xx][yy])continue;
            mp[xx][yy]=0;
            node to;
            to.x=xx,to.y=yy;
            q.push(to);
        }
    }
}

int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++){
        char S[1010];
        scanf("%s",S);
        for(int j=0;j<n;j++)
            mp[i][j+1]=(S[j]!='0');
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            if(mp[i][j])Bfs(i,j),ans++;
    printf("%d",ans);
}
View Code

 

C:Volume_体积

题目描述
    给出n件物品,每件物品有一个体积Vi,求从中取出若干件物品能够组成的不同的体积和有多少种可能。例如,n=3,V=(1,3,4),那么输出6,6种不同体积和具体为1、34578。

输入
    第1行1个正整数,表示n。
    第2行n个正整数,表示Vi,每两个数之间用一个空格隔开。

输出
    一行一个数,表示不同的体积和有多少种可能。

样例输入
1 3 4

样例输出
6

数据规模
    对于30%的数据满足:n≤5,Vi≤10。
    对于60%的数据满足:n≤10,Vi≤20。
    对于100%的数据满足:n≤20,1≤Vi≤50
题面

  深搜的水水水水水题啊,前三道都好水。

#include<cstdio>

bool t[1010];
int n,ans;
int a[24];

void Dfs(int x,int tot){
    if(x==n+1){
        t[tot]=1;
        return;
    }
    Dfs(x+1,tot);
    Dfs(x+1,tot+a[x]);
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    Dfs(1,0);
    for(int i=1;i<=1000;i++)
        ans+=t[i];
    printf("%d",ans);
}
View Code

 

D:Decompuse_数的拆分

题目描述
    输人一个整数n,输出n拆分成若干正整数和的所有方案,即n=S1+S2+..+Sk.的形式,且S1≤S2≤...Sk,n≤20,请按照字典序输出。

输入
    一行一个整数n。

输出
    所有拆分方案,具体格式参见输出样例。

样例输入
4

样例输出
1+1+1+1
1+1+2
1+3
2+2
4
total=5
题面

  使用数组a存储当前拆分的过程,当剩余部分被拆分完毕时,输出即可。

#include<cstdio>

int n,a[21],ans;

int out(int t){
    int j;
    for(j=1;j<=t-1;j++)
        printf("%d+",a[j]);
    printf("%d\n",a[t]);
}

void huafen(int sum,int t){
    int i;
    if(sum==0){
        out(t-1);
        ans++;
        return;
    }
    for(i=1;i<=sum;i++)
        if(a[t-1]<=i&&-i<=n){
            a[t]=i;
            sum=sum-i;
            huafen(sum,t+1);
            sum=sum+i;
        }
}
int main(){
    scanf("%d",&n);
    huafen(n,1);
    printf("total=%d",ans);
}
View Code

 

E:Form_全排列问题

  啊真的好水啊啊啊啊。

  不上题面了。就是给出数n,按照字典序输出1-n的全排列,1<n<=9。

  没有用深搜回溯了,直接用的next_permutation()函数。

#include<cstdio>
#include<algorithm>

int n,a[10];

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)a[i]=i;
    do{ for(int i=1;i<=n;i++)
            printf("%d",a[i]);
        putchar('\n');
    }while(std::next_permutation(a+1,a+1+n));
}
View Code

 

F:Arrange_最佳调度问题

题目描述
    假设有n个任务由k个可并行工作的机器完成。完成任务i需要的时间为ti。试设计一个算法找出完成这n个任务的最佳调度,使得完成全部任务的时间最早。
    对任意给定的整数n和k,以及完成任务i需要的时间为ti,i=1~n。编程计算完成这n个任务的最佳调度。

输入
    第一行有2 个正整数n和k。第2 行的n个正整数是完成n个任务需要的时间。

n<=20 k<=20 ti<=100

输出
    一行一个数,表示完成全部任务的最早时间。

样例输入
7 3
2 14 4 16 6 5 3

样例输出
17
题面

  貌似是一道贪心,按完成任务所需时间从大到小安排给当前剩余时间最大的机器?但事实证明乱贪是没有前途的。

  正解实际上就是一道基础的深搜剪枝。

#include<stdio.h>
#include<iostream>
#include<algorithm>

const int N=1010;
int n,k,t[N],s[N],ans=0x3f3f3f3f;

void Dfs(int p,int c){
    if(ans<=c) return;
    if(p==n+1){
        ans=std::min(ans,c);
        return;
    }
    for(int i=1;i<=k;++i)
        if(s[i]+t[p]<ans){
            s[i]+=t[p];
            Dfs(p+1,std::max(s[i],c));
            s[i]-=t[p];
        }
}

bool cmp(int a,int b){
    return a>b;
}

int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i) 
        scanf("%d",&t[i]);
    std::sort(t+1,t+n+1,cmp);
    Dfs(1,0);
    printf("%d",ans);
} 
View Code

 

G:Egypt_埃及分数

题目描述
    在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。
    如:
19/45=1/3 + 1/12 + 1/180
19/45=1/3 + 1/15 + 1/45
19/45=1/3 + 1/18 + 1/30
19/45=1/4 + 1/6 + 1/180
19/45=1/5 + 1/6 + 1/18.
    最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
    给出a,b(0<a<b<1000),编程计算最好的表达方式。

输入
    一行两个自然数,用一个空格隔开,表示a、b。

输出
    若干个数,自小到大排列,依次是单位分数的分母。

样例输入
19 45

样例输出
5 6 18
题面

  考察迭代加深与搜索剪枝。

  dep(搜索深度),mol(分子),den(分母),pre(上一个分母)。

#include<cstdio>
#include<iostream>
#define LL long long

int lim,ans;
bool flag;
int num[1010],tot[1010];

void Dfs(LL dep,LL mol,LL den,LL pre) {
    if(dep==lim+1){
        if(mol==0)
            flag=true;
            if(num[lim]<tot[lim]){
                for(LL i=1;i<=lim;i++)tot[i]=num[i];
                ans=num[lim];
            }
        return;
    }
    if((den*(lim+1-dep))/mol>ans||num[dep]>ans)return; 
    for(LL i=std::max(pre,den/mol);i<=den*(lim+1-dep)/mol;i++){
        num[dep]=i; 
        Dfs(dep+1,mol*i-den,den*i,i+1); 
    }
}

int main(){
    int a,b;
    scanf("%d%d",&a,&b);
    for(lim=1;;lim++){
        tot[lim]=0x3f3f3f3f;
        ans=0x3f3f3f3f;
        Dfs(1,a,b,1);
        if(flag)break;
    }
    for(LL i=1;i<=lim;i++)
        printf("%d ",tot[i]);
    putchar('\n');
}
View Code

 

H:Gold_金币问题II

题目描述
    国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八九、十天),每天收到四枚金币,....这种工资发放模式会-直这样延续下去。当连续N天每天收到N枚金币后,骑士会在之后的连续N+1天里,每天收到N+1枚金币。
    请编程计算在从第一天开始的给定天数内,骑士一共获得了多少金币。

输入
    输入包含至少一行,单不多于1000行
    除最后一行外,输入的每行是一组输入数据,包含一个正整数n,表示天数。
    输入的最后一行为0,表示输入结束。

输出
    对每个数据输出一行一个整数,表示该数据对应的金币总数。

样例输入
10
6
7
11
15
16
100
10000
1000
21
22
0

样例输出
30
14
18
35
55
61
945
942820
29820
91
98

提示
对于60%的数据满足:n<=10^3
对于80%的数据满足:n<=10^6
对于100%的数据满足:n<=10^12
题面

  原题金币本来是一道循环结构入门题,但是本题,很明显要用数学的方法。

  题目的关键信息即是“得到N枚金币的天数为N,第一天得到1枚金币”。易由分析得:

    1:(单日)收到N枚金币的第N天为总天数中的第N*(N+1)/2天;

    2:收到N枚金币的第N天,总共收到了1*1+2*2+3*3+...+N*N枚金币。

  故可以预处理出前10^12天中:

    1:数组a存储收到N枚金币的第N天为总天数中的哪一天;

    2:数组c存储收到N枚金币的第N天总共收到多少枚金币。

  每次输入天数,进行二分查找(这里直接用STL了),分段处理。

#include<cstdio>
#include<algorithm>
#define LL long long

const int Size=2e6+1;

LL n,ans;
LL a[Size],c[Size];

int main(){
    for(int i=1;i<Size;i++)
        a[i]=1ll*i*(i+1)/2,c[i]=1ll*i*i+c[i-1];
    while(~scanf("%lld",&n)){
        if(!n)return 0;
        ans=0;
        int pos=std::lower_bound(a+1,a+Size,n)-a;
        --pos;
        ans+=c[pos]+(n-a[pos])*(pos+1);
        printf("%lld\n",ans);
    }
}
View Code

 

I:Cake_生日蛋糕

  深搜剪枝题。

  搜索过程中的参数传值有v(当前已用体积),s(已有表面积),res(剩余层数),r(半径),h(高)。

  剪枝如下:

    可行性剪枝:体积超出,表面积超出;

    最优化剪枝:当前的表面积加上余下的侧面积大于当前最优。

  剩下的搜索就是从下一层的最大可行半径开始向小枚举,再枚举高。

#include<cmath>
#include<cstdio>
#include<iostream>

const int INF=0x7f7f7f7f;

int area[201],vol[201],m,n,ans;

void dfs(int v,int s,int res,int r,int h){
    int i,j,yy;
    if(res==0){
        if(v==n&&s<ans)ans=s;
        return ;
    }
    if(v+vol[res-1]>n)return;
    if(s+area[res-1]>ans)return;
    if(2*(n-v)/r+s>=ans)return;
    for(i=r-1;i>=res;i--){
        if(res==m)s=i*i;
        yy=std::min((n-v-vol[res-1])/(i*i),h-1);
        for(j=yy;j>=res;j--)
            dfs(v+i*i*j,s+2*i*j,res-1,i,j);
    }
}

int main() {
    scanf("%d%d",&n,&m);
    ans=INF;
    area[0]=vol[0]=0;
    for(int i=1;i<21;i++)
        area[i]=area[i-1]+2*i*i,
        vol[i]=vol[i-1]+i*i*i;
    dfs(0,0,m,n+1,n+1);
    if(ans==INF)printf("0");
    else printf("%d",ans);
}
View Code

 

 

最后安利一首あの夢をなぞって-YOASOBI

(尝试夹带私货啊哈哈哈)

posted @ 2021-09-14 11:22  Echo_ln  阅读(130)  评论(0)    收藏  举报