此次复习直接跟着《挑战程序设计竞赛》这本书走,既看例题复习,用通过后面的习题来进行练手。
DFS
POJ 1979:Red and Black
红与黑,算是深搜比较经典的题了,大意是求能走到的黑色格子有多少。从起点开一个dfs就可以,但是我还是习惯性地写bfs。
简单题直接上代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXW 25
#define MAXH 25
using namespace std;
int w,h;
int f[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
char map[MAXW][MAXH];
struct node
{
int x,y;
}st;
int dfs(int x,int y)
{
int cnt=0;
map[x][y]='#';
for(int i=0;i<4;i++)
{
int nx=x+f[i][0];
int ny=y+f[i][1];
if(map[nx][ny]=='#'||nx<1||ny<1||nx>w||ny>h)
continue;
cnt+=dfs(nx,ny);
}
return cnt+1;
}
void Init()
{
memset(map,0,sizeof map);
st.x=st.y=0;
}
int main()
{
while(1)
{
Init();
scanf("%d%d",&h,&w);
if(!w)
return 0;
for(int i=1;i<=w;i++)
{
scanf("%s",map[i]+1);
for(int j=1;j<=h;j++)
if(map[i][j]=='@')
st.x=i,st.y=j;
}
printf("%d\n",dfs(st.x,st.y));
}
}
AOJ 0118:Property Distribution
因为是日本人写的书,所以自然有些题是日文,但是绝大部分都可以在百度上找到翻译,此处不再复制。大意就是求有多少个区域,不同的水果相互4联通(即4个方向相邻算联通)算一个区域。
从头开始做dfs,把能拓展的点全部访问一遍标记vis,遇到已经标记的点就跳过,最后相当于统计做了多少次dfs。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXW 105
#define MAXH 105
using namespace std;
int w,h,ans;
int f[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
bool vis[MAXH][MAXW];
char map[MAXH][MAXW];
void dfs(int x,int y,char wh)
{
vis[x][y]=1;
for(int i=0;i<4;i++)
{
int nx=x+f[i][0];
int ny=y+f[i][1];
if(map[nx][ny]!=wh||vis[nx][ny]||nx<1||ny<1||nx>h||ny>w)
continue;
dfs(nx,ny,wh);
}
}
void Init()
{
memset(map,0,sizeof map);
memset(vis,0,sizeof vis);
ans=0;
}
int main()
{
while(1)
{
Init();
scanf("%d%d",&h,&w);
if(!h)
return 0;
for(int i=1;i<=h;i++)
scanf("%s",map[i]+1);
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
{
if(vis[i][j])
continue;
ans++;
dfs(i,j,map[i][j]);
}
printf("%d\n",ans);
}
}
AOJ 0033:Ball
题目大意是将10个球通过挡板使其落向左或右,使得两边的球都是升序。
我的做法是直接进行dfs,使其满足升序的条件,然后检查最后的球是否能够放完,即为问题的解。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,ln,rn;
int a[15],l[15],r[15];
bool dfs(int x)
{
if(x==11)
return 1;
bool flag=0;
if(a[x]>l[ln])
{
l[++ln]=a[x];
flag|=dfs(x+1);
ln--;
}
if(a[x]>r[rn])
{
r[++rn]=a[x];
flag|=dfs(x+1);
rn--;
}
return flag;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
memset(l,0,sizeof l);
memset(r,0,sizeof r);
ln=rn=0;
for(int j=1;j<=10;j++)
scanf("%d",&a[j]);
if(dfs(1))
printf("YES\n");
else
printf("NO\n");
}
}
POJ 3009:Curling 2.0
题目大意,有一个特殊的冰壶,它会沿冰面一直运动到它撞到一个障碍,此时障碍小时,并且你可以使其向另一个方向运动(可以是之前相同的方向),除此之外冰壶没有办法停下。最终要求你找到使冰壶从起点到终点的最小步数。
我认为这道题是一个普通的dfs,只不过在进行状态转移的时候将向周围拓展一个改为拓展到障碍为止。另外为了方便还原现场,可以用栈来保存一下去除的障碍。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#define MAXW 25
#define MAXH 25
#define INF 0x3fffffff
using namespace std;
int w,h,ans;
int map[MAXH][MAXW];
int f[4][2]={{0,-1},{0,1},{1,0},{-1,0}};
struct node
{
int x,y;
node(){}
node(int a,int b)
{
x=a,y=b;
}
}st;
stack <node> dis;
int run1(int x,int y,int i)
{
if(i==0)
return x;
if(map[x+i][y]==1)
return -1;
while(1)
{
int nx=x+i;
if(nx<1||nx>h)
return -1;
if(map[nx][y]==1)
{
dis.push(node(nx,y));
map[nx][y]=0;
return x;
}
if(map[nx][y]==3)
return 0;
x=nx;
}
}
int run2(int x,int y,int i)
{
if(i==0)
return y;
if(map[x][y+i]==1)
return -1;
while(1)
{
int ny=y+i;
//printf("***%d\n",ny);
if(ny<1||ny>w)
return -1;
if(map[x][ny]==1)
{
dis.push(node(x,ny));
map[x][ny]=0;
return y;
}
if(map[x][ny]==3)
return 0;
y=ny;
}
}
void dfs(int x,int y,int cnt)
{
//printf("*%d*%d*%d----\n",x,y,cnt );
if(cnt>=10)
return;
for(int i=0;i<4;i++)
{
int nx=run1(x,y,f[i][0]);
int ny=run2(x,y,f[i][1]);
//printf("**%d*%d*\n",nx,ny);
if(nx==-1||ny==-1)
continue;
if(nx==0||ny==0)
{
ans=min(ans,cnt+1);
return;
}
dfs(nx,ny,cnt+1);
node t=dis.top();
dis.pop();
map[t.x][t.y]=1;
}
return;
}
int main()
{
while(1)
{
ans=INF;
memset(map,0,sizeof map);
while(!dis.empty())
dis.pop();
scanf("%d%d",&w,&h);
if(!h)
return 0;
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j]==2)
st.x=i,st.y=j;
}
//printf("%d-%d\n",st.x,st.y);
dfs(st.x,st.y,0);
printf("%d\n",ans==INF?-1:ans);
}
}
BFS
AOJ 0558: Cheese
题目大意,按照顺序访问1到n个点。
直接做n遍bfs,将得到的结果相加即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define MAXH 1005
#define MAXW 1005
using namespace std;
int h,w,n,ans;
int f[4][2]={{0,-1},{1,0},{-1,0},{0,1}};
char map[MAXH][MAXW];
struct node
{
int x,y,cnt;
node(){}
node(int a,int b,int c)
{
x=a,y=b,cnt=c;
}
}a[15];
void bfs(node s,node e)
{
queue <node> q;
bool vis[MAXH][MAXW];
memset(vis,0,sizeof vis);
q.push(s);
vis[s.x][s.y]=1;
while(!q.empty())
{
node t=q.front();
//printf("--%d*%d\n",t.x,t.y);
q.pop();
for(int i=0;i<4;i++)
{
int nx=t.x+f[i][0];
int ny=t.y+f[i][1];
if(vis[nx][ny]||nx<1||ny<1||nx>h||ny>w||map[nx][ny]=='X')
continue;
if(nx==e.x&&ny==e.y)
{
ans+=t.cnt+1;
return;
}
vis[nx][ny]=1;
q.push(node(nx,ny,t.cnt+1));
}
}
}
int main()
{
scanf("%d%d%d",&h,&w,&n);
for(int i=1;i<=h;i++)
{
scanf("%s",map[i]+1);
for(int j=1;j<=w;j++)
{
if(map[i][j]=='S')
a[0].x=i,a[0].y=j;
if(map[i][j]>='1'&&map[i][j]<='9')
a[map[i][j]-'0'].x=i,a[map[i][j]-'0'].y=j;
}
}
for(int i=1;i<=n;i++)
bfs(a[i-1],a[i]);
printf("%d\n",ans);
}
POJ 3669:Meteor Shower
题目大意,将会有一些流星砸向地面,你需要找到一个安全的地方,即永远不会被流星砸到的地方。但是被流星摧毁的地方你是不能走的。流星会在某个时刻摧毁它所坠落的一格以及相邻的四格,求到达安全点的最短时间。
可以先将会被流星摧毁的格子信息保存起来,而存的东西就是流星摧毁此处的时间。然后在进行bfs时,通过到达此点的时间和此点被摧毁的时间进行比较,判断是否可以通过。然而要特别注意,题目中并没有说必须移动,所以需要增加一个保持原地不动的状态,或者对原点进行特殊判断。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 0x3fffffff
using namespace std;
int n;
int map[405][405];
int f[5][2]={{1,0},{-1,0},{0,1},{0,-1},{0,0}};
struct node
{
int x,y,cnt;
node(){}
node(int a,int b,int c)
{
x=a,y=b,cnt=c;
}
};
void bfs()
{
queue <node> q;
bool vis[405][405];
memset(vis,0,sizeof vis);
q.push(node(0,0,0));
vis[0][0]=1;
while(!q.empty())
{
node t=q.front();
//printf("**%d-%d-%d\n",t.x,t.y,t.cnt);
q.pop();
if(map[t.x][t.y]==INF)
{
printf("%d",t.cnt);
return;
}
if(map[t.x][t.y]<=t.cnt)
continue;
for(int i=0;i<5;i++)
{
int nx=t.x+f[i][0];
int ny=t.y+f[i][1];
if(nx<0||ny<0||nx>400||ny>400||vis[nx][ny]||t.cnt+1>=map[nx][ny])
continue;
vis[nx][ny]=1;
q.push(node(nx,ny,t.cnt+1));
}
}
printf("-1");
return;
}
int main()
{
for(int i=0;i<=405;i++)
for(int j=0;j<=405;j++)
map[i][j]=INF;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x,y,t;
scanf("%d%d%d",&x,&y,&t);
for(int j=0;j<5;j++)
{
int nx=x+f[j][0];
int ny=y+f[j][1];
if(nx<0||ny<0||nx>400||ny>400)
continue;
map[nx][ny]=min(map[nx][ny],t);
//printf("*%d*%d\n",nx,ny);
}
}
bfs();
return 0;
}
AOJ 0121:Seven Puzzle
这道题可以说是八数码的弱化版。看到这道题是第一个反应是状压,然后就想到hash。但是仔细一想,8的8次方似乎可以接受,于是就用的普通的数位压缩。接着思考是在线做还是离线做,即问一次答一次或问完后一起达(请仔细思考这两种方法所用的时间)。在线肯定是会超时间的,而我脑残了,认为离线做的空间复杂度是也是8的8次方会爆内存,而实际上只有8的阶乘。最后恍然大悟,自学map做了离线。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
map <int,int> vis;
int st;
int eg[9];
int a[8]={1,8,64,512,4096,32768,262144,2097152};
int f[4]={-1,4,1,-4};
struct node
{
int state;
int cnt;
node(){}
node(int a,int b)
{
state=a,cnt=b;
}
};
queue <int> q;
int zy()
{
int tot=0,cnt=0;
for(int i=8;i>0;i--)
{
cnt=a[tot++]*eg[i]+cnt;
}
return cnt;
}
void jy(int x)
{
int tot=7;
for(int i=1;i<=8;i++)
{
eg[i]=x/a[tot];
x%=a[tot--];
}
}
void Init()
{
q.push(342391);
vis[342391]=0;
while(!q.empty())
{
int t=q.front();
q.pop();
jy(t);
for(int i=1;i<=8;i++)
if(eg[i]==0)
{
for(int j=0;j<4;j++)
{
int nx=i+f[j];
if(nx<1||nx>8)
continue;
if((i==4&&nx==5)||(i==5&&nx==4))
continue;
swap(eg[i],eg[nx]);
//for(int qe=1;qe<=8;qe++)
// printf("*%d",eg[qe]);
int ne=zy();
//printf("--%d",ne);
swap(eg[nx],eg[i]);
if(vis[ne]!=0||ne==342391)
continue;
vis[ne]=vis[t]+1;
q.push(ne);
//printf("\n");
}
}
}
}
int main()
{
Init();
while(~scanf("%d",&eg[1]))
{
for(int i=2;i<=8;i++)
scanf("%d",&eg[i]);
int now=zy();
printf("%d\n",vis[now]);
}
}
穷竭搜索
POJ 2718:Smallest Difference
题目大意,给定一个集合,选其中一些数组成一个新的数,剩下的数组成另一个数,要求这两个数之差的绝对值最小,求这个最小值。
可以两个dfs跑,枚举出两个数,答案取min即可。但是这样做肯定会超时的,因此需要剪枝。我们可以发现,当我们构造第二个数时,如果已经构造的部分后面全部补上0所得到的答案已经不优于当前得到的解,那么就剪去。这个最优性剪枝可以砍掉一大半的时间。
另外听说贪心可以做出来,欢迎大家尝试。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define INF 0x3fffffff
using namespace std;
int n,cnt,best;
int a[15];
bool vis[15];
bool check(int x,int y,int t)
{
if(y==0)
return 1;
int tot=1;
for(int i=1;i<=t;i++)
tot*=10;
//printf("********%d\n",y*tot);
if(abs(x-(y*tot))>best)
return 0;
return 1;
}
void dfs2(int x,int last,int num)
{
if(!check(last,num,cnt-(cnt/2)-x))
{
return;
}
if(x==cnt-(cnt/2))
{
//printf("---%d\n",num);
best=min(best,abs(num-last));
return;
}
for(int i=1;i<=cnt;i++)
{
if(vis[i])
continue;
if(a[i]==0&&cnt!=2&&x==0)
continue;
vis[i]=1;
dfs2(x+1,last,num*10+a[i]);
vis[i]=0;
}
}
void dfs1(int x,int num)
{
if(x==cnt/2)
{
//printf("*%d\n",num);
dfs2(0,num,0);
return;
}
for(int i=1;i<=cnt;i++)
{
if(vis[i])
continue;
if(a[i]==0&&cnt!=2&&x==0)
continue;
vis[i]=1;
dfs1(x+1,num*10+a[i]);
vis[i]=0;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
best=INF;
memset(vis,0,sizeof vis);
cnt=0;
char c;
while(1)
{
scanf("%d%c",&a[++cnt],&c);
if(c=='\n')
break;
}
dfs1(0,0);
printf("%d\n",best);
}
}
POJ 3187:Backward Digit Sums
题目大意,数字金字塔倒着来做。给定最终的结果和初始有几个数,求字典序最小的解。
我们可以倒着来解这道题,直接进行全排列,判断是否符合题意即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,sum;
int a[15];
bool vis[15];
bool check()
{
int b[15];
memcpy(b,a,sizeof a);
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i;j++)
b[j]=b[j]+b[j+1];
//printf("**%d\n",b[1]);
if(b[1]==sum)
return 1;
return 0;
}
void dfs(int x)
{
if(x==n+1)
{
if(check())
{
for(int i=1;i<n;i++)
printf("%d ",a[i]);
printf("%d",a[n]);
exit(0);
}
return ;
}
for(int i=1;i<=n;i++)
{
if(vis[i])
continue;
vis[i]=1;
a[x]=i;
dfs(x+1);
vis[i]=0;
}
}
int main()
{
scanf("%d%d",&n,&sum);
dfs(1);
}
POJ 3050:Hopscotch
题目大意,给定5*5的地图,走6步,求得到的不同序列有多少种。
dfs直接搜,判重用数位压缩一下,压成一个6位数进行判重就好了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int map[10][10],ans;
int f[4][2]={{0,-1},{0,1},{-1,0},{1,0}};
bool vis[1000000],last[10][10];
void dfs(int x,int y,int cnt,int num)
{
if(cnt==6)
{
//printf("--%d--%d\n",num,ans);
if(!vis[num])
ans++;
vis[num]=1;
return;
}
for(int i=0;i<4;i++)
{
int nx=x+f[i][0];
int ny=y+f[i][1];
if(nx<1||ny<1||nx>5||ny>5)
continue;
dfs(nx,ny,cnt+1,num*10+map[nx][ny]);
}
}
int main()
{
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++)
scanf("%d",&map[i][j]);
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++)
dfs(i,j,0,0);
printf("%d\n",ans);
}
AOJ 0525:Osenbei
题目大意,给定一些烧饼,你可以同时翻一列,或同一行,使得最后朝上的烧饼数最多,求这个最大值。
因为数据范围的特殊性,我们可以01枚举行翻还不翻。然后统计纵向统计这个状态下,面朝上的烧饼有多少个。因为操作不限制,所以取一个max就可以保证是最多的。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int r,c,best;
int map[15][10005];
int num()
{
int sum=0;
for(int j=1;j<=c;j++)
{
int cnt=0;
for(int i=1;i<=r;i++)
{
if(map[i][j]==1)
cnt++;
}
sum+=max(cnt,r-cnt);
}
return sum;
}
void work(int a)
{
for(int i=1;i<=c;i++)
map[a][i]=map[a][i]==0?1:0;
}
void dfs(int x)
{
if(x==r+1)
{
best=max(best,num());
return;
}
work(x);
dfs(x+1);
work(x);
dfs(x+1);
}
int main()
{
while(1)
{
scanf("%d%d",&r,&c);
if(!r)
return 0;
best=0;
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++)
scanf("%d",&map[i][j]);
dfs(1);
printf("%d\n",best);
}
}