ACM的第一乐章--DFS(入门)

DFS以一棵树的形状呈现的满是美的搜索结构,对于任何类型的搜索,DFS均可以暴力解之,然而给它好的剪枝,会使得代码简单而富有效率。

 

A Knight's Journey

http://poj.org/problem?id=2488

题意:给一个p*q的棋盘,按象棋里的马的‘日’字规则把所有的点仅且只走一次,输出最小字典序走法。

思路:常规的dfs可能会超时(没试过),所以要做优先探测处理,保证每次进去的都是最优的。

 

View Code
#include<stdio.h>
#include<string.h>
int p,q,ok;
int dx[]={-1,1,-2,2,-2,2,-1,1};
int dy[]={-2,-2,-1,-1,1,1,2,2};//保证最优处理方向
int patha[100],pathb[100],cnt;
int mmap[100][100];

void dfs(int x,int y,int cn)
{
if(x<1 || y<1 || x>p || y>q || mmap[x][y]) return;

patha[cn] = x; pathb[cn] = y;
mmap[x][y]=1;
if(cn == p*q){ ok = 1; cnt = cn; return ;}


for(int i = 0; i < 8; ++ i){
int xx = dx[i] + x;
int yy = dy[i] + y;
dfs(xx,yy,cn+1);
if(ok)return ;
}

mmap[x][y]=0;
}

int main()
{
int t,cas=0;
scanf("%d",&t);
while(t--){
scanf("%d%d",&p,&q);
memset(mmap,0,sizeof(mmap));
ok=0;
for(int i = 1; i <= q; ++ i)
for(int j = 1; j <= p; ++ j){
dfs(j,i,1);
if(ok)break;
}
printf("Scenario #%d:\n",++cas);
if(!ok)puts("impossible");
else{
for(int i=1; i<=cnt; ++i)
printf("%c%d",pathb[i]+'A'-1,patha[i]);
puts("");
}
puts("");
}return 0;
}

 

Children of the Candy Corn

http://poj.org/problem?id=3083

题意:给你一个h * w的迷宫,然后问你沿着迷宫墙壁的左边走和沿着迷宫墙壁的右边走,各需多少步,然后最少需要多少步。

 

思路:dfs+记忆化。

沿墙壁左,右两边走直接dfs可以很容易就出来,确定方向是个很麻烦的事~~~,观察到当往左走时,每次都是测试当前方向的左边,然后是上边、右边、下边。这正是一个很好的循环,所以我们只要记住当前方向就好了。

求最少步那就是简单的dfs+记忆化了。

View Code
#include<stdio.h>
#include<string.h>

int w,h;
int r,l,m;
int si,sj,di,dj,ok;
char as[100][100];
int dx[]={0,-1,0,1};
int dy[]={-1,0,1,0};
int bs[100][100];

int judgeL(int cur)
{
if(cur == 1) return 0;
if(cur == 2) return 1;
if(cur == 3) return 2;
return 3;
}

int judgeR(int cur)
{
if(cur == 1) return 2;
if(cur == 2) return 3;
if(cur == 3) return 0;
return 1;
}

void dfs(int x,int y,int kd,int cur,int cnt)
{

if( x<0 || y<0 || x>=h || y>=w || as[x][y]=='#' ) return ;
if(kd == 1){
if((x == di) && (y == dj)){ l = cnt; ok = 1; return ;}
int T = 0;
cur = judgeL(cur);//向左边走时,以当前方向判断下一方向
while(T < 4){//很好的循环处理
int xx = x + dx[cur];
int yy = y + dy[cur];
if( xx<0 || yy<0 || xx>=h || yy>=w || as[xx][yy]=='#' );
else break;
++cur;
cur %= 4;
T++;
}
dfs(x + dx[cur],y + dy[cur],kd,cur,cnt+1);
}

if(kd == 2){
if((x == di) && (y == dj)){ r = cnt; ok = 1; return ;}
int T = 0;
cur = judgeR(cur);//同左边
while(T < 4){
int xx = x + dx[cur];
int yy = y + dy[cur];
if( xx<0 || yy<0 || xx>=h || yy>=w || as[xx][yy]=='#' );
else break;
--cur;
cur = (cur + 4) % 4;
T++;
}
dfs(x + dx[cur],y + dy[cur],kd,cur,cnt+1);
}

if(kd == 3){
if( m<=cnt || bs[x][y] <= cnt ) return ;
bs[x][y] = cnt;
if((x == di) && (y == dj)){ m = cnt; return ;}
int T = 0;
as[x][y] = '#';
cur = judgeL(cur);
while(T < 4){
dfs(x + dx[cur],y + dy[cur],kd,cur,cnt+1);
++cur;
cur %= 4;
T++;
}
as[x][y] = '.';
}
}

int main()
{
int i,j,t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&w,&h);
l = r =0;
for(i = 0; i < h; ++ i) scanf("%s",as[i]);
for(i = 0; i < h; ++ i)
for(j = 0; j < w; ++ j){
if(as[i][j] == 'S'){ si = i; sj = j; }
else if(as[i][j] == 'E'){ di = i; dj = j; }
bs[i][j] = 100000;
}
ok = 0;
dfs(si,sj,1,-1,1);
ok = 0;
dfs(si,sj,2,-1,1);
m = l > r? r : l;
dfs(si,sj,3,-1,1);
printf("%d %d %d\n",l,r,m);
}return 0;
}

展开字符串

http://acm.hdu.edu.cn/showproblem.php?pid=1274

题意:在纺织CAD系统开发过程中,经常会遇到纱线排列的问题。

该问题的描述是这样的:常用纱线的品种一般不会超过25种,所以分别可以用小写字母表示 不同的纱线,例如:abc表示三根纱线的排列;重复可以用数字和括号表示,例如:2(abc)表示abcabc;1(a)=1a表示a;2ab表示 aab;如果括号前面没有表示重复的数字出现,则就可认为是1被省略了,如:cd(abc)=cd1(abc)=cdabc;

 

思路:题目意思够简单吧,如果不用dfs,那该怎么做?(ranklist里有人500+b的代码过了,还真不知道怎么写),做了这个题后,我才知道dfs具有如此优美的搜索结构,就像听小提琴演奏一样,简单却美到心里。

遇到括号就dfs,然后将dfs返回的字符串接到当前串后面(多简单)。

View Code
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char str[10000],ans[10000000];
int len;
bool isabc(char ch)
{
if( ch>='a'&&ch<='z' ) return true;
return false;
}
bool isnum(char ch)
{
if( ch>='0'&&ch<='9' ) return true;
return false;
}
int solve(int s,char cs[])
{
int i=s,la,lb,lt,c=0;
char as[10000],tmp[10000];
while( i<len ){
if( isnum(str[i]) ){
int cnt=0;
while( i<len&&isnum(str[i]) ) { cnt=cnt*10+str[i]-'0'; i++; }
if( str[i]=='(' ){
i=solve(i+1,tmp);
as[c]='\0';
while( cnt ){
strcpy(as+c,tmp);
c=(int)strlen(as);
cnt--;
}
}
else{
while( cnt ){
as[c++]=str[i];
cnt--;
}
}
}
else if( str[i]=='(' ){
i=solve(i+1,tmp);
strcpy(as+c,tmp);
c=(int)strlen(as);
}
else if( str[i]==')' ) break;
else as[c++]=str[i];
i++;
}
as[c]='\0';
strcpy(cs,as);
return i;
}
int main()
{
int t;
scanf("%d",&t);
while( t-- )
{
scanf("%s",str);
len=(int)strlen(str);
solve(0,ans);
puts(ans);
}return 0;
}

Curling 2.0

http://poj.org/problem?id=3009

题意:

就是要求把一个冰壶从起点“2”用最少的步数移动到终点“3”

其中0为移动区域,1为石头区域,冰壶一旦想着某个方向运动就不会停止,也不会改变方向(想想冰壶在冰上滑动),除非冰壶撞到石头1 或者 到达终点 3

注意的是:

冰壶撞到石头后,冰壶会停在石头前面,此时(静止状态)才允许改变冰壶的运动方向,而该块石头会破裂,石头所在的区域由1变为0. 也就是说,冰壶撞到石头后,并不会取代石头的位置。

终点是一个摩擦力很大的区域,冰壶若到达终点3,就会停止在终点的位置不再移动。最多允许移10次。

 

 思路:良好的dfs结构:以当前位置向四个方向走,如果能走,则dfs。

View Code
#include<stdio.h>
#include<string.h>
int as[50][50];
int h,w,ans;
int dx[] = {0,-1,0,1};
int dy[] = {-1,0,1,0};

bool isok(int x,int y)
{
return !(x<1 || y<1 || x>h || y>w || as[x][y]);
}

void dfs(int si,int sj,int cnt)
{
int i,j,k,t,x,y;
if(cnt >= ans ) return ;
if( as[si][sj] == 3 ){
ans = cnt;
return ;
}


for(i = 0; i < 4; ++ i){
x = si + dx[i];
y = sj + dy[i];
if( as[x][y] == 3 ) ans = cnt;
if(isok(x,y)){
while(isok(x,y)){//一直走到尽头
x += dx[i];
y += dy[i];
}
if(as[x][y]){
if( as[x][y] == 3){ ans = cnt; return; }
else{
as[x][y] = 0;
dfs(x - dx[i],y - dy[i],cnt+1);//注意,不是以x,y位置dfs
as[x][y] = 1;
}
}
}
}
}

int main()
{
while(scanf("%d%d",&w,&h)==2){
if(w + h == 0)break;
int si,sj;
memset(as,0,sizeof(as));
for(int i = 1; i <= h; ++i)
for(int j = 1; j <= w; ++j){
scanf("%d",&as[i][j]);
if( as[i][j] == 2 ){ si = i; sj = j;}
}
ans = 11;
as[si][sj] = 0;
dfs(si,sj,1);
if( ans == 11 ) puts("-1");
else printf("%d\n",ans);
}return 0;
}

棋盘问题

http://poj.org/problem?id=1321

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

思路:和八皇后问题差不多,暴力。

View Code
#include<stdio.h>
#include<string.h>
char as[10][10];
int col[10];
int total,ans,n,k,m;

void dfs(int x,int cnt)
{
int flag = 0;
if( cnt == k ){ ++ans; return ;}
if(x>n)return;

for(int i = 1; i<= n; ++ i){
if( !col[i]&&as[x][i]=='#'){
col[i] = 1;
dfs(x+1,cnt+1);
col[i] = 0;
}
}
dfs(x+1,cnt);
}
int main()
{
while(scanf("%d%d",&n,&k)==2){
if(n + k<0)break;
memset(as,0,sizeof(as));
for(int i = 1; i <= n; ++ i){scanf("%s",as[i]+1);}
memset(col,0,sizeof(col));
ans = 0;
m = 0;
dfs(1,0);
printf("%d\n",ans);
}return 0;
}

Network Saboteur

http://poj.org/problem?id=2531

题意:网络中有n个节点,可以把这n个节点划分成两个集合subsets A和subsets B,同一个集合中的节点间进行通讯没有时间损失,不同集合中的节点间进行通讯会有时间损失,求一种对n个结点的划分方法,使得时间的总损失最大。

思路:简单dfs+剪枝

 

分成两个集合,那么对于每个点来说只有选与不选两种情况(和背包相似,不知道能不能用背包做,还有人用随机算法做的,不懂)。剪枝方法很简单,如果剩下的节点都不会被选进来,加上当前已选进来节点的通讯总量比已得到的最大值还小,就可以返回了。

View Code
#include<stdio.h>
#include<string.h>
int as[100][100],n,ans,total;
int vis[100];
void dfs(int x,int cur,int res)
{
int i,j,k=cur,t=res;
if( x==n ) return ;
if( ans>=cur+res ) return ;// 如果剩下的节点都不会被选进来,加上当前已选进来节点的通讯总量比已得到的最大值还小,就可以返回了。
for( i=0; i<n; i++ ) if( !vis[i] ) cur+=as[x][i]; else{ t-=as[x][i]; cur-=as[x][i]; }
//cur为x点要选进A集合时候知得到的通讯总量
if( cur>ans ) ans=cur;
vis[x]=1;
dfs(x+1,cur,res);//把x选入A集合
vis[x]=0;
dfs(x+1,k,res);//x不选入A集合
}
int main()
{
int i,j;
while( scanf("%d",&n)==1 ){
total=ans=0;
memset(vis,0,sizeof(vis));
for( i=0; i<n; i++ ) for( j=0; j<n; j++ ) { scanf("%d",&as[i][j]); total+=as[i][j]; }
dfs(0,0,total);
printf("%d\n",ans);
}return 0;
}

Square

http://acm.hdu.edu.cn/showproblem.php?pid=1518

题意(好题):所有的木棒都要用上,问是否能构成正方形(最多只有20条木棒)。

思路:最初以为只有20条木棒,觉得怎么dfs都不会超时(现在也想不明白有什么数据可以卡住,除非数据很大),结果~~~,看了人家的代码才知道,关键要想明白每选一条边只可能向前走,不可能还测探前面的(边要排好序)。

View Code
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
int as[100],vis[100],ok,n,k;
int cmp(int a,int b)
{
return a<b;
}

void dfs(int d,int cur,int cnt)
{
if( cnt==3 ) ok=1;
if( ok ) return ;
if( cur==k ){
dfs(-1,0,cnt+1);//从1开始
return ;
}
int i,j,x=-1;
for( i=d+1; i<n; i++ )//不要从1开始
{
if( !vis[i]&&as[i]!=x ){
x=as[i];
vis[i]=1;
if( cur+as[i]<=k ) dfs(i,cur+as[i],cnt);
else{ vis[i]=0; return ;}
vis[i]=0;
}
}
}
int main()
{
int i,j,cas;
scanf("%d",&cas);
while( cas-- )
{
scanf("%d",&n);
k=0; j=0;
for( i=0; i<n; i++ ) { scanf("%d",as+i); k+=as[i]; if( as[i]>j ) j=as[i]; }
ok=0;
if( k%4==0 ){
k/=4;
// if( j>k ) break;
sort(as,as+n,cmp);
memset(vis,0,sizeof(vis));
vis[0]=1;
dfs(-1,0,0);
}
if( !ok ) puts("no");else puts("yes");
}return 0;
}

Sudoku

http://poj.org/problem?id=2676

题意:给一个未完成的九宫数独,要求其中的一个解。

思路:很简单的dfs+剪枝。

剪枝很容易的就想到如果当前空格可以填入的数字是哪些,然后从这些选好了,这关键就是要把当前位置的行和列及当前所在宫的已存在数字全部标记出来,每次填入后,又进行标记。

(测试数据出得不好,正向搜索和反向搜索的时间相差30倍)

View Code
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<vector>
#include<set>
using namespace std;
int as[20][20],ok;
int s[10][10]; //每宫已存在的数字
int col[10][10]; //每列已存在的数字
int row[10][10];//每行已存在的数字

int judge(int i,int j)//判断i,j位置所在的宫
{
if(i<=3){
if(j<=3)return 1;
if(j<=6)return 2;
return 3;
}
if(i<=6){
if(j<=3)return 4;
if(j<=6)return 5;
return 6;
}
if(j<=3)return 7;
if(j<=6)return 8;
return 9;
}
void dfs(int x,int y)
{
if( x == 0 ){
ok = 1;
return ;
}
while(y <= 9){ if(!as[x][y])break; y++;}
if( y == 10){
dfs(x-1,1);
return ;
}
int k = judge(x,y);
for(int i = 1; i <= 9; ++ i){
if(!s[k][i] && !col[y][i] && !row[x][i])//判断
{
as[x][y] = i;
s[k][i] = 1;
col[y][i] = 1;
row[x][i] = 1;
if( y == 9) dfs(x-1,1);
else dfs(x,y+1);
if(ok) return ;
s[k][i] = 0;
col[y][i] = 0;
row[x][i] = 0;
as[x][y] = 0;
}
}
}

int main()
{
int t;
char str[20][20];
scanf("%d",&t);
while(t--){
for(int i = 1; i <= 9; ++ i)scanf("%s",str[i]+1);
for(int i = 1; i <= 9; ++ i)
for(int j = 1; j <= 9; ++ j) as[i][j] = str[i][j] - '0';
memset(s,0,sizeof(s));
memset(col,0,sizeof(col));
memset(row,0,sizeof(row));
for(int i = 1; i <= 9; ++ i)
for(int j = 1; j <= 9; ++ j){
if(as[i][j]){
s[judge(i,j)][as[i][j]] = 1;
col[j][as[i][j]] = 1;
row[i][as[i][j]] = 1;
}
}
ok = 0;
dfs(9,1);
for(int i = 1; i <= 9; ++ i){
for(int j = 1; j <= 9; ++ j)printf("%d",as[i][j]);
puts("");
}
}return 0;
}

posted on 2012-03-28 23:46  aigoruan  阅读(574)  评论(0)    收藏  举报

导航