枚举
case1:百鸡问题,
三重循环的,在判断/3时,因为是取整是去尾的,我用了(z+2)/3,这样就不会因为去尾造成问题了
#include<iostream>
using namespace std;
int main() {
int n;
while(scanf("%d", &n)!=EOF) {
int xn=min(n/5,100);
int yn=min(n/3,100);
int zn=min(3*n,100);
for(int x=0; x<=xn; x++)
for(int y=0; y<=yn; y++)
for(int z=0; z<=zn; z++) {
if(x+y+z==100 && 5*x+3*y+(z+2)/3<=n)
printf("x=%d,y=%d,z=%d\n", x,y,z);
}
}
return 0;
}
其实可以优化为二重循环(王道上 循环变量x和y的范围分别是100和100-x,这样z就一定是>=0的了,我写的还需要判断z>=10,容易出错)
#include<iostream>
using namespace std;
int main() {
int n;
while(scanf("%d", &n)!=EOF) {
int xn=min(n/5,100);
int yn=min(n/3,100);
int zn=min(3*n,100);
for(int x=0; x<=xn; x++)
for(int y=0; y<=yn; y++) {
int z=100-x-y;
if(z>=0&&x*15+y*9+z<=n*3)
printf("x=%d,y=%d,z=%d\n", x,y,z);
}
}
return 0;
}
case2:找出所有满足abc+bcc=532的abc。
- 首先发现c必须是1或6,所以bcc必须是b11 or b66,这样在循环的时候,可以大步走了,然后循环x时也可以每次+=5快步走,然后再按位判断 满足条件的输出即可
#include<iostream>
using namespace std;
int main() {//10:43->10:51
//c必须是1或6
for(int x=101; x<=432; x+=5) {
for(int y=111; y<=432; ) {
if(x+y==532 && x%10==y%10 && x/10%10==y/100)
printf("%d %d %d\n",x/100,y/100,x%10);
if(y%10==1) y+=55;
else y+=45;
}
}
return 0;
}
网上还看到一种方案,是按 每位来的
#include<stdio.h>
int main() {
int a,b,c;
for(a=0; a<10; a++) {
for(b=0; b<10; b++) {
for(c=0; c<10; c++) {
if(a*100+b*10+c + b*100+c*10+c == 532 ) {
printf("%d %d %d\n",a,b,c);
}
}
}
}
return 0;
}
case3:Old Bill,金钱5位数,只能看到中间3位,给出买了的火鸡数,问想要最贵的鸡对应的首尾数字以及鸡的单价
- 判断整除的方法:double(s)/n==s/n
- 想要最贵的鸡对应的数字,就要从大的数开始遍历,找到一个就stopji
#include<iostream>
using namespace std;
int main() {//10:57->11:07
int n;//火鸡数 1~99
int a,b,c;
while(scanf("%d%d%d%d", &n,&a,&b,&c)!=EOF) {
int k=a*1000+b*100+c*10;
int ok=0;
for(int i=9; i>=1&&!ok; i--) {
for(int j=9; j>=0&&!ok; j--) {
int s=i*10000+k+j;
if(double(s)/n==s/n) {
printf("%d %d %d\n", i,j,s/n);
ok=1;
}
}
}
if(!ok) printf("0\n");
}
return 0;
}
BFS
注意的点:
- input是某形状的时候,清楚 x和y到底是控制那个坐标!
case1:胜利大逃亡。板子题,记好咯。注意这里在maze是int,若是char可能会出现读入空格或换行,可以在%c前面加上空格解决该问题
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=51;
int maze[N][N][N];//0是路,1是墙
int vis[N][N][N];
int a,b,c,ti;//a*b*c的maze,ti是限定时间
struct node {
int x,y,z;
int t;
} s,v; //不过是两个结构体变量, 后面经常会用到,没有特别含义,
//若说有,那就是s更像temp,v用来表示next node(其实就是一个s表示不下了而已
queue<node>q;
int go[6][3]= {0,0,1, 0,0,-1, 0,1,0, 0,-1,0, 1,0,0, -1,0,0}; //6个方向
int BFS() {
vis[0][0][0]=1;
s.x=s.y=s.z=s.t=0;
q.push(s);
while(!q.empty()) {
s=q.front();
if(s.x==a-1 && s.y==b-1 && s.z==c-1)
return s.t;
q.pop();
for(int i=0; i<6; i++) {
int nx=s.x+go[i][0];//0开始!!整错了
int ny=s.y+go[i][1];
int nz=s.z+go[i][2];
if(nx<0 || ny<0 || nz<0 || nx>=a || ny>=b || nz>=c)
continue;//出界
if(vis[nx][ny][nz]) continue;//已访问
if(maze[nx][ny][nz]==1) continue;//是墙
vis[nx][ny][nz]=1;
v.x=nx;
v.y=ny;
v.z=nz;
v.t=s.t+1;
q.push(v);
}
}
return -1;//BFS完了都没return,说明没出去
}
int main() { //15:22->16:0?
int T;
cin>>T;
while(T--) {
memset(vis,0,sizeof(vis));//maze不用init,因为input限定死了
while(!q.empty()) q.pop();
scanf("%d%d%d%d", &a,&b,&c,&ti);
for(int i=0; i<a; i++)
for(int j=0; j<b; j++)
for(int k=0; k<c; k++) {
scanf("%d", &maze[i][j][k]);
}
int ans=BFS();
printf("%d\n", ans<=ti?ans:-1);
}
return 0;
}
case2:非常可乐,非常好的题,不是类似maze的明显的BFS,其实搜索本质就是对抽象出来的状态空间进行搜索,so 如何抽象是需要多加练习的
- 这里要求的最少倾倒次数,其实就是一个“最短路”,BFS处理相当合适。而这里的状态空间就是三个容器的容积,状态转移就是排列数A32=6种倾倒,太棒了!
- 倒可乐这个函数写的很好(使用“引用”,以后就用引用吧。指针的话,传参加上&,函数里面全部加上*),节省了很多代码量,我刚开始顺序搞错了,因为修改一个体积的时候,后面修改时可能用到前面的体积,如果前面修改了,可能到处出错
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=102;
struct node {
int a,b,c;//其实就是三个容器而已了
int t;
} s,v;
queue<node>q;
int vis[N][N][N];
int V,x,y;//体积,两杯子容量
void A2B(int &a, int va, int &b, int vb) { //原始可乐体积,杯子容量。最后得到A->B后的新可乐体积
if(a>vb-b) { //a的可乐在vb里装不下
a-=vb-b;//顺序不能颠倒啊!
b=vb;
}
else {
b+=a;
a=0;
}
}
int check(int a,int b,int c) {
int k=V/2;
if(a==k&&b==k || b==k&&c==k || a==k&&c==k)
return 1;
return 0;
}
int BFS() {//这题有最短路的意味,BFS很合适
s.a=V;
s.b=s.c=s.t=0;
q.push(s);
vis[V][0][0]=1;
while(!q.empty()) {
s=q.front();
q.pop();
int a=s.a;
int b=s.b;
int c=s.c;
// cout<<a<<' '<<b<<' '<<c<<'\n';
A2B(a,V,b,x);//a->b
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
a=s.a;
b=s.b;
c=s.c;
A2B(b,x,a,V);//b->a
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
a=s.a;
b=s.b;
c=s.c;
A2B(a,V,c,y);//a->c
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
a=s.a;
b=s.b;
c=s.c;
A2B(c,y,a,V);//c->a
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
a=s.a;
b=s.b;
c=s.c;
A2B(b,x,c,y);//b->c
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
a=s.a;
b=s.b;
c=s.c;
A2B(c,y,b,x);//c->b
if(!vis[a][b][c]) {
if(check(a,b,c))
return s.t+1;
vis[a][b][c]=1;
v.a=a;
v.b=b;
v.c=c;
v.t=s.t+1;
q.push(v);
}
}
return 0;
}
int main() {
while(scanf("%d%d%d", &V,&x,&y)!=EOF) {
if(!V&&!x&&!y) break;
if(V&1) printf("NO\n");//prune,奇数不能平分
else {
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
int ans=BFS();
if(ans) printf("%d\n", ans);
else printf("NO\n");
}
}
return 0;
}
case3:Acute Stroke,就是简单的求“体积”,也就是联通块,但是犯了两个非常经典的错误,小结一下:
- 三维数组a[i][j][k]问题,其实不论怎么安排道理都是一样的,我是这样的,因为L表示切片数,mn是一个matrix,我这样input个人比较好理解:
注意的是,对应坐标的数组容量开对+BFS越界处理对。看了别人的代码,这个地方其实无论怎么input都行,就是x和y轴换一样,这个立方体是一样的,在for的时候,不要想成嵌套,只当做是对一个轴的赋值
- v.cnt=s.cnt+1,这样表达的是深度(层次数、“步数”),而这里是点数,也就是每一次“q.front()”都是要记录一次
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int a[62][1288][130];
int vis[62][1288][130];
int m,n,l,t;
struct node {
int x,y,z;
int cnt;
} s,v;
queue<node>q;
int go[6][3]= {0,0,1,0,0,-1,
1,0,0,-1,0,0,
0,1,0,0,-1,0
};
int BFS(int x,int y,int z) {
int ans=0;
s.x=x;
s.y=y;
s.z=z;
s.cnt=0;
q.push(s);
vis[x][y][z]=1;
while(!q.empty()) {
s=q.front();
// cout<<s.x<<' '<<s.y<<' '<<s.z<<endl;
ans++;
q.pop();
for(int i=0; i<6; i++) {
int nx=s.x+go[i][0];
int ny=s.y+go[i][1];
int nz=s.z+go[i][2];
if(vis[nx][ny][nz] || nx>l||ny>m||nz>n || nx<1||ny<1||nz<1) continue;
if(a[nx][ny][nz]==1) {
v.x=nx;
v.y=ny;
v.z=nz;
v.cnt=s.cnt+1;
q.push(v);
vis[nx][ny][nz]=1;
}
}
}
if(ans>=t) return ans;
else return 0;
}
int main() { //coding:19:40->
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
cin>>m>>n>>l>>t;
for(int i=1; i<=l; i++) //这是z轴吧?
for(int j=1; j<=m; j++)
for(int k=1; k<=n; k++)
scanf("%d", &a[i][j][k]);
int ans=0;
for(int i=1; i<=l; i++) //这是z轴吧?
for(int j=1; j<=m; j++)
for(int k=1; k<=n; k++)
if(vis[i][j][k]==0&&a[i][j][k]==1)
ans+=BFS(i,j,k);
cout<<ans;
return 0;
}
递归与DFS
case1:汉诺塔III 要求必须经过中间的柱子,因为只要求移动次数,可以得到递推关系式子。另外注意输出格式是lld
#include<iostream>
using namespace std;
unsigned long long F(unsigned long long n) {
if(n==1) return 2;
else return 3*F(n-1)+2;
}
int main() {
unsigned long long n;//1<=n<=35
while(scanf("%lld", &n)!=EOF){
printf("%lld\n", F(n));
}
return 0;
}
case2:素数环问题 就是环内任意两个数的sum必须是素数。这里有些技巧
- 大思路就是DFS,因为每次遍历一个数字不断往后试,不行就backtrace,典型的DFS【思考了一下,BFS是按照某种目标,逐渐递增,so常常处理最优路等问题;而DFS则有“延伸”的感觉,这个素数环就很典型,包括排列数等,日后再好好体会】
- 用表存prime换时间,因为题目给出n<=16,所以只要列到最大的两个数的sum15+16=31这个数量的素数即可(我开始表都写错了,21不是prime....)
- 边界条件十分重要!比如刚开始时,n>1了才能去check,不然只要一个数,不存在sum,永远都return
#include<iostream>
#include<cstring>
using namespace std;
const int N=17;//因为我是1~16,所以是17!
int prime[]= {2,3,5,7,11,13,17,19,23,29,31}; //15+16=31,最多到31就够了
int n;
int ans[N];
int vis[N];
int check(int x) {
for(int i=0; i<11; i++) { //到31是第12个素数
if(x==prime[i]) return 1;
}
return 0;
}
void DFS(int num) {
if(num>1 && check(ans[num-1]+ans[num])==0) //num>1时才可以前后比较呀!
return ;
if(num==n && check(ans[n]+ans[1])) {
for(int i=1; i<=n; i++)
printf("%d ",ans[i]);
printf("\n");
return ;
}
for(int i=2;i<=n;i++){
if(!vis[i]) {
vis[i]=1;
ans[num+1]=i;
DFS(num+1);
vis[i]=0;//回溯
}
}
}
int main() {
for(int i=1;; i++) {
cin>>n;
memset(vis,0,sizeof(vis));
ans[1]=vis[1]=1;
printf("Case %d:\n", i);
DFS(1);
}
return 0;
}
case3:Oil Deposit,判断联通块个数。DFS/BFS都可以,DFS代码精简,速度也不见得慢多少,比较没有也回溯,一条路DFS到头就行了,这里DFS的功能就是把同一个联通块都标记上,注意:
- 不需要DFS后vis[nx][ny]=0,(不要回溯)因为就是想标记完。而岔路口的问题,其实在方向的for循环里控制好了,只要一条路DFS走到底就行了
- 一个scanf技巧就是for(int i=0; i<m; i++) scanf("%s", &maze[i]); 直接读成字符数组,且这里的取值&可以不写
#include<iostream>
#include<cstring>
using namespace std;
const int N=101;
char maze[N][N];
int vis[N][N];
int m,n;
int go[8][2]= {0,1,1,0,0,-1,-1,0,
1,1,-1,-1,-1,1,1,-1
};//8方向
int DFS(int x,int y) {
for(int i=0; i<8; i++) {
int nx=x+go[i][0];
int ny=y+go[i][1];
if(nx<0||ny<0||nx>=m||ny>=n) continue;
if(!vis[nx][ny] && maze[nx][ny]=='@') {
vis[nx][ny]=1;
DFS(nx,ny);
// vis[nx][ny]=0;//不要回溯,就是想标记完。而岔路口的问题,其实在方向的for循环里控制了,只要一条路DFS走到底就行了
}
}
}
int main() {
while(scanf("%d%d", &m,&n)!=EOF, m!=0) {
memset(vis,0,sizeof(vis));
for(int i=0; i<m; i++)
scanf("%s", &maze[i]);
int cnt=0;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++) {
if(!vis[i][j] && maze[i][j]=='@') {
DFS(i,j);//这里DFS的目的就是把联通的标记完毕
cnt++;
}
}
cout<<cnt<<endl;
}
return 0;
}
case4:全排列,牛客和codeup OJ都有问题,本题正常能AC的代码应该如下,牛客的需要加上sort(因为并没有按照题目要求有小到大输入,so要手动升序排列)注意:
- 记得return
- 用scanf的话,获取真实长度要用strlen(s.c_str()),用length得到的是resize的size
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
string s,ans;
int vis[26];
int n;
void DFS(int x) {
if(x==n) {
cout<<ans<<endl;
return ;//若不return,那数字放完后还要执行下面的for循环,而恰好vis都是0,可以永远递归下去,于是程序终止了?
}
else for(int i=0; i<n; i++) {//写else其实就可以不写return了
if(!vis[i]) {
vis[i]=1;
ans+=s[i];
DFS(x+1);
vis[i]=0;
ans.pop_back();
}
}
}
int main() {
s.resize(10);//scanf("%s", &s[0])
while(scanf("%s", &s[0])) {
memset(vis,0,sizeof(vis));
n=strlen(s.c_str());//不能用s.length()这个和s.size()一样,是resize的长度一样
ans="";
// sort(s.begin(),s.begin()+n);//牛客的问题
DFS(0);
printf("\n");//或者用puts("") 会自动加上换行
}
return 0;
}
使用STL的next_permutation函数:这个能在codeup AC,但其实是不满足题意的,因为next_per函数是返回比当前“大”的排列,所以312得到的结果只有312和321,所以也需要sort变成123,这样才能有应该的6种答案。具体用法如下:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
string a;
int main() {
int n;
a.resize(10);
while(scanf("%s",&a[0])!=EOF) {//cin也AC,不要加!cin.eof()反而错了。。
n=strlen(a.c_str());
do {
printf("%s\n",a.c_str());
} while(next_permutation(a.begin(),a.begin()+n));//不能到a.end() 但是用cin的话是可以的
printf("\n");//或者用puts("") 会自动加上换行
}
return 0;
}
我自己基于DFS也写了一个像next_permutation那种,得到比原本“大”的排列,方法是用一个变量控制后面的排列是否可以“随便排”,比如原序列是213,排第一位的时候,必须是>=2才能排,如果排了2,第二位还是不能随便排,必须是>=1,但是若第一位排了3,后面的就可以随便排了。但是我没实现“去重”,即111的排列结果会是6个111
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
string s,ans;
int vis[26];
int n;
int DFS(int x, int is) {//位置x,is:后面是否可以随便排
if(x==n) {
cout<<ans<<endl;
}
for(int i=0; i<n; i++) {
if(!vis[i]) {
if(is==0) {//必须是>=原来位置的数值,才可以放
if(s[i]>s[x]) {
vis[i]=1;
ans+=s[i];
is=1;
DFS(x+1,is);
is=0;
vis[i]=0;
ans.pop_back();
}
else if(s[i]==s[x]) {
vis[i]=1;
ans+=s[i];
DFS(x+1,is);//后面也是要判断必须是>=到能放
vis[i]=0;
ans.pop_back();
}
}
else if(is==1) { //可以随便放
vis[i]=1;
ans+=s[i];
DFS(x+1,1);
vis[i]=0;
ans.pop_back();
}
}
}
}
int main() {
s.resize(10);//scanf("%s", &s[0])
while(scanf("%s", &s[0])) {
memset(vis,0,sizeof(vis));
n=strlen(s.c_str());//不能用s.length()这个和s.size()一样,是resize的长度一样
ans="";
// sort(s.begin(),s.begin()+n);
DFS(0,0);
printf("\n");//或者用puts("") 会自动加上换行
}
return 0;
}
case5:Tempter of the bone 要判断是否可以在规定的时间点从S点到D点。又一起比较了DFS与BFS,这是存在性问题,适合DFS,且不能用BFS,因为BFS是在按照“最短路”走,这题完全不是这个意思,就是需要弯弯绕然后在规定时间到达终点。有一个非常棒的剪枝操作:
- 先把起点终点找到,如果两点奇偶性相同,那么一定是偶数时间到达,反正则必须是奇数时间到达@如果不满足这个条件的一定不可以走到,而不用DFS啦。我的实现就是很直接,按照语义来,王道上面用的
- (dx+dy)%2 == ((sx+sy)%2+t%2)%2 表示满足奇偶条件,可读性差
- 其实就是(dx+dy+sx+sy)%2 == t%2 啦,合在一起写了。但是为什么把%2换成&1会出错呢?
- 先算术运算,后移位运算,最后位运算!so:((dx+dy+sx+sy)&1) != (t&1)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10;
char maze[N][N];
int vis[N][N];
int m,n,t,ans;
int sx,sy,dx,dy;//开始位置和门的位置
int go[4][2]= {0,1,1,0,0,-1,-1,0};
void DFS(int x,int y,int step) {
if(step==t && maze[x][y]=='D') {
ans=1;
return ;
} else if(step>t || ans)
return ;
for(int i=0; i<4; i++) {
int nx=x+go[i][0];
int ny=y+go[i][1];
if(nx<0||ny<0||nx>=m||ny>=n) continue;
if(!vis[nx][ny] && (maze[nx][ny]=='.'||maze[nx][ny]=='D')) {//别忘了D是终点,当然可以走
vis[nx][ny]=1;
// cout<<step<<' '<<x<<y<<endl;
DFS(nx,ny,step+1);
vis[nx][ny]=0;
}
}
}
int main() {
while(scanf("%d%d%d", &m,&n,&t)!=EOF) {
if(!m&&!n&&!t) break;
memset(vis,0,sizeof(vis));
ans=0;
for(int i=0; i<m; i++)
scanf("%s", &maze[i]);
for(int i=0,ok_s=0,ok_d=0; i<m&&(!ok_s||!ok_d); i++)
for(int j=0; j<n&&(!ok_s||!ok_d); j++) {
if(maze[i][j]=='S') {
sx=i;
sy=j;
ok_s=1;
}
else if(maze[i][j]=='D') {
dx=i;
dy=j;
ok_d=1;
}
}
//华丽的prune:奇偶性同,但t为奇数,不行;反之也不行
// if((sx+sy)&1==(dx+dy)&1 && t&1==1 || (sx+sy)&1!=(dx+dy)&1 && t&1==0) {
if(((dx+dy+sx+sy)&1) != (t&1)){
// cout<<((dx+dy)&1)<<' '<<((sx+sy+t)&1)<<' '<<t<<endl;
printf("NO\n");
}
else {
DFS(sx,sy,0);
printf("%s\n",ans?"YES":"NO");
}
}
return 0;
}