目录
Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫书P194例题7-4)
DFS(Depth First Search)
算法竞赛中的一个重要技巧,在许多题目里,用DFS有着神奇的作用。
利用栈这种数据结构来实现(找到的第一个解不一定是最优解,只是先序遍历最早的可行解)
案例解释:走迷宫
看到哪个方向可以走就走哪个,而且你没有办法分身,所以只能慢慢试探,不撞南山不回头。
数塔问题
题目描述:
输入一个三角形塔,从三角塔顶出发向下走,每个点都有不同的权值,走到那个点就获得对应的权值,求走到塔底的时候能够获得的权值的最大值。
Sample Input
4
5
8 4
3 6 9
7 2 9 5
Sample Output
28
先来一个最普通的代码。
///数塔 1.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <sstream>
#define MAX 100010
using namespace std;
int n;
int a[200][200];
int DFS(int i,int j)
{
if(i==n)
return a[i][j];
int x = DFS(i+1,j);
int y = DFS(i+1,j+1);
return max(x,y) + a[i][j];
}
int main()
{
while(cin>>n)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
cout<<DFS(1,1)<<endl;
}
return 0;
}
这个做法很直观,但是每一次都要从头开始搜,有大量的重复计算,这样非常耗时,需要优化。
可以另外加一个vis数组来存储上一次计算的结果,这样就可以节省计算时间。
///数塔 2.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <sstream>
#define MAX 100010
using namespace std;
int n;
int a[200][200];
int vis[200][200];
int DFS(int i,int j)
{
if(vis[i][j]!=-1)
return vis[i][j];
if(i==n)///end of this road
vis[i][j] = a[i][j];///save the result
else
{
int x = DFS(i+1,j);
int y = DFS(i+1,j+1);
vis[i][j] = max(x,y) + a[i][j];
}
return vis[i][j];
}
int main()
{
while(cin>>n)
{
memset(a,0,sizeof(a));
memset(vis,0xff,sizeof(vis));
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
cout<<DFS(1,1)<<endl;
}
return 0;
}
Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫书P194例题7-4)
题目描述:输入一个正整数n,把整数1,2,3,...,n组成一个环,使得相邻两个整数之和均为素数,输出时从整数1开始逆时针排列。同一个环输出一次。(n<=16)
Sample Input
6 8
Sample Output
Case 1:
1 4 3 2 5 6
1 6 5 2 3 4
Case 2:
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2
分析:最大的数是16,如果把所有可能结果都生成然后一个个试。。。肯定超时。
这里可以用DFS回溯,即用深度优先遍历解答树(可参考紫书的解释)
另外注意输出格式,案例之间有一个空行
///Prime Ring Problem
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int vis[25]={0};
int n,num[25]={0};
int is_prime(int k)///判断素数,也可以打表,那样更快
{
int i;
for(i=2;i<=sqrt(k);i++)
if(k%i==0)
return 0;
return 1;
}
void DFS(int k)
{
int i;
if(k>n&&is_prime(num[n]+num[1]))///测试最后一个数和第一个数之和是否为素数
{
for(i=1;i<n;i++)
printf("%d ",num[i]);
printf("%d\n",num[i]);
}
else
{
for(i=2;i<=n;i++)///尝试放置每个数i
{
if(!vis[i]&&is_prime(i+num[k-1]))///i没有用过且与前一个数之和为素数
{
vis[i]=1;
num[k]=i;
DFS(k+1);
vis[i]=0;
}
}
}
}
int main()
{
int cnt=1;
while(cin>>n)
{
if(n<1||n>19)
break;
printf("Case %d:\n",cnt++);
num[1]=1;
DFS(2);
printf("\n");
}
return 0;
}
Zipper HDOJ - 1501(DFS+剪枝)
题目描述:给出两个字符串,问能否在不改变字符串本身顺序的情况下,拆开重组成指定字符串,输出yes/no。
Sample Input
3
cat tree tcraete
cat tree catrtee
cat tree cttaree
Sample Output
Data set 1: yes
Data set 2: yes
Data set 3: no
( ˙˘˙ )没想到吧,这题居然也能用DFS。
分析:从三个串的首元素开始,遇到一串/二串与目标串匹配的字母,继续向下DFS,如果这些可以到达目标串的尾元素,那么意味着前面的都匹配成功,所以这个结果是yes,否则no。
//Zipper HDOJ 1501
#include <cstdio>
#include <iostream>
using namespace std;
char a[209],b[209],c[409];
int vis[209][209]={0};
int OK=0;
void DFS(int i,int j,int k)
{
if(vis[i][j]==1)///已经访问过,剪掉
return;
if(c[k]=='\0')///到指定字符串的结尾,说明之前的都匹配成功
{
OK=1;
return;
}
if(a[i]!='\0'&&c[k]==a[i])//匹配成功
DFS(i+1,j,k+1);
if(b[j]!='\0'&&c[k]==b[j])//匹配成功
DFS(i,j+1,k+1);
vis[i][j]=1;///cut
}
int main()
{
int n,i,j,cnt=0;
cin>>n;
while(n--)
{
OK=0;
for(i=0;i<209;i++)///cut
for(j=0;j<209;j++)
vis[i][j]=0;
scanf("%s%s%s",a,b,c);
DFS(0,0,0);
printf("Data set %d: %s\n",++cnt,OK?"yes":"no");
}
return 0;
}
Lake Counting POJ - 2386
题目描述:给出一张n*m的地图,求图中水洼的个数。水洼的大小不定,如果W的四周也有W,这些W组成一个大的水洼。
Sample Input
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
Sample Output
3
分析:找到‘W’的地,然后对其四周进行判断(DFS),并且把它附近全部变成‘.’,计算这样做的次数,即为池塘的个数。
//Lake Counting POJ2386
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
char farm[109][109];
int n,m;
void dfs(int x,int y)
{
farm[x][y]='.';
int i,j;
int tx,ty;
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
{
tx=x+i;
ty=y+j;
if((tx>=0&&tx<n)&&(ty>=0&&ty<m)&&farm[tx][ty]=='W')
dfs(tx,ty);
}
}
}
int main(void)
{
int i,j;
while(cin>>n>>m)
{
int sum=0;
getchar();
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
cin>>farm[i][j];
getchar();
}
for(i=0;i<n;i++)
for(j=0;j<m;j++)
{
if(farm[i][j]=='W')
{
dfs(i,j);
sum++;
}
}
cout<<sum<<endl;
}
return 0;
}
棋盘问题 POJ - 1321
题目描述:给出一个n*m的棋盘,‘#’可以放棋子,求满足所有放下的棋子都不在同一行同一列的情况有多少种。(类似于八皇后问题)
Sample Input
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
Sample Output
2
1
分析:用DFS,满足条件则往下一个位置继续,如果放下的棋子已经达到要求的个数,则方法+1。
//POJ - 1321
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
///notice that the '#' can be placed
int n,m;
int ans=0;
char map[10][10];
int vis[10];
void DFS(int x,int step)
{
if(step == m)///found a way that work
{
ans++;
return;
}
if(x==n)
return;
DFS(x+1,step);
for(int j=0; j<n; j++)
{
if(vis[j]==0 && map[x][j]=='#')///这行这列都没放棋子
{
vis[j]=1;
DFS(x+1,step+1);
vis[j]=0;///for different situations
}
}
}
int main()
{
while(cin>>n>>m)
{
if(n==-1 && m==-1)
break;
ans=0;
memset(vis,0,sizeof(vis));
for(int i=0; i<n; i++)
scanf("%s",map[i]);
DFS(0,0);
printf("%d\n",ans);
}
return 0;
}
水果消除 HNUSTOJ
题目描述:给出一个n*n的表格,表格元素是数字,相同的数字表示相同的水果,两个或以上相邻的水果可以消除,(消除之后其他水果不会改变位置),问可以消除的方案数。
Sample Input
6
1 1 2 2 2 2
1 3 2 1 1 2
2 2 2 2 2 3
3 2 3 3 1 1
2 2 2 2 3 1
2 3 2 3 2 2
Sample Output
6
分析:用DFS找某个数字周围的相同数字,如果相同就沿着这个数字向下继续找,如果相同的数字个数大于等于2,那么这样的一次搜索就是一个方案,搜索过的数字变成0,代表已经搜索过,最终输出方案数。
///水果消除 HNUSTOJ
#include <cstdio>
#include <iostream>
using namespace std;
///notice that the '#' can be placed while '.' not
int map[1000][1000];
int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};
int n;
int num;
void DFS(int x,int y,int k)
{
map[x][y]=0;
int i;
int tx,ty;
for(i=0;i<4;i++)
{
tx=x+dir[i][0];
ty=y+dir[i][1];
if(tx>=0 && tx<n && ty>=0 && ty<n && (map[tx][ty] == k))
{
num++;
DFS(tx,ty,k);
}
}
}
int main()
{
while(cin>>n)
{
int sum=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&map[i][j]);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(map[i][j])
{
num=1;
DFS(i,j,map[i][j]);
if(num>1)
sum++;
}
printf("%d\n",sum);
}
return 0;
}
=======遇到好的题目会回来继续更新======
===如果各位大牛牪犇路过发现问题欢迎反映 (「・ω・)「嘿===