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 的 0、1 矩阵,表示海洋地图。 最后一行为四个小于 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(); }
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); }
C:Volume_体积
题目描述 给出n件物品,每件物品有一个体积Vi,求从中取出若干件物品能够组成的不同的体积和有多少种可能。例如,n=3,V=(1,3,4),那么输出6,6种不同体积和具体为1、3、4、5、7、8。 输入 第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); }
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); }
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)); }
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); }
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'); }
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); } }
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); }
最后安利一首あの夢をなぞって-YOASOBI
(尝试夹带私货啊哈哈哈)
浙公网安备 33010602011771号