深度优先搜索的几道题目

感觉还是比动规简单一些的……但是最近脑子有点糊。
首先是要注意邻接表是有两种的,在稀疏图中,最好用vector存每个顶点的边的信息,而不是稠密图中的G[x][y]。
第二是步数最少的路径……存path然后把path拷贝到bestpath……要不然会乱。我犯了很多次傻然后后面debug。这个错误很蠢但是我一开始也没想清楚。
下面是一些具体问题。

百练2815 城堡问题
基本上可以算一个连通图的模板题了。但是有几个点。首先是如何统计房间的数量?我反正是没想到,把不同的房间用不同的数字染色。统计颜色的数量就好了。然后就是把visited返回要放在dfs外面……要不然逻辑上有问题,我也是蠢了。
最后AC代码:

//百练2815:城堡问题
#include 
#include 
using namespace std;
const int maxm = 51;
int dx[] = {0, -1, 0, 1};
int dy[] = {-1, 0, 1, 0};
int visited[maxm][maxm];
int m, n;
int map[maxm][maxm];
int legal(int x, int y) {
	if (x < 0 || x >= m || y < 0 || y >= n) return 0;
	return 1;
}
int roomnum;
int maxroomsize;
int cursize;
int dfs(int x, int y) {
	++cursize;
	visited[x][y] = roomnum;
	for (int i = 0; i < 4; ++i) {
		if (!legal(x + dx[i], y + dy[i])) continue;
		if (visited[x + dx[i]][y + dy[i]]) continue;
		if (((map[x][y] >> i) & 1)) continue;
		visited[x + dx[i]][y + dy[i]] = roomnum;
		dfs(x + dx[i], y + dy[i]);
	}
	return 1;
}
int main() {
	cin >> m >> n;
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < n; ++j) {
			cin >> map[i][j];
		}
	}
	int init = 1;
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < n; ++j) {
			cursize = 0;
			if (visited[i][j]) continue;
			++roomnum;
			dfs(i, j);
			maxroomsize = max(maxroomsize, cursize);
		}
	}
	cout << roomnum << endl;
	cout << maxroomsize << endl;
	return 0;
}

或者下面这种:

//百练2815:城堡问题
#include 
#include 
using namespace std;
const int maxm = 51;
int dx[] = {0, -1, 0, 1};
int dy[] = {-1, 0, 1, 0};
int visited[maxm][maxm];
int m, n;
int map[maxm][maxm];
int legal(int x, int y) {
	if (x < 0 || x >= m || y < 0 || y >= n) return 0;
	return 1;
}
int roomnum;
int maxroomsize;
int cursize;
int dfs(int x, int y, int & cursize) {
	//++cursize;
	visited[x][y] = roomnum;
	for (int i = 0; i < 4; ++i) {
		if (!legal(x + dx[i], y + dy[i])) continue;
		if (visited[x + dx[i]][y + dy[i]]) continue;
		if (((map[x][y] >> i) & 1)) continue;
		visited[x + dx[i]][y + dy[i]] = roomnum;
		cursize += 1;
		dfs(x + dx[i], y + dy[i], cursize);
	}
	maxroomsize = max(maxroomsize, cursize);
	return 1;
}
int main() {
	cin >> m >> n;
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < n; ++j) {
			cin >> map[i][j];
		}
	}
	int init = 1;
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < n; ++j) {
			//cursize = 0;
			if (visited[i][j]) continue;
			++roomnum;
			dfs(i, j, init);
			init = 1;
			//maxroomsize = max(maxroomsize, cursize);
		}
	}
	cout << roomnum << endl;
	cout << maxroomsize << endl;
	return 0;
}

百练1724 ROADS
我其实看了课件才有思路的……好吧。就是要把cost拿出来作为一维度存中间结果。
这个题就是注意道路不止一条,所以要用vector存,不能用邻接矩阵。
另外值得一提的是有一个剪枝会导致WA。就是如果下一个目的地不是N而道路的花费加上已经有的花费等于K的时候剪枝。这个是错的因为有可能某条路花销为0.
还有就是我尝试用动规做,也就是“我为人人”,状态转移方程为:

minlength[roadinfo[i][j].dest][cost] = min(minlength[roadinfo[i][j].dest][cost], minlength[i][k] + roadinfo[i][j].length)

但是实际上是不行的。因为要考虑到回头路的存在,因此搜索顺序就成了个问题,因为现在就不能从i=1搜到i=N了。
最后AC代码:

#include 
#include 
#include 
#include 
using namespace std;
const int MAXN = 102;
const int MAXK = 10002;
const int MAXR = 10002;
const int INF = (1 << 30);
int N, K, R;
struct road{
	int cost;
	int length;
};
vector  roadinfo[MAXN][MAXN];
//int roadinfo[MAXN][MAXN][2];
int minlength[MAXN][MAXK];
int output = INF;
int visited[MAXN];
int dfs(int location, int cost, int curlength) {
	if (location == N) {
		if (cost <= K) {
			minlength[N][cost] = min(minlength[N][cost], curlength);
			output = min(output, minlength[N][cost]);
			return 1;
		}
	}
	for (int i = 2; i <= N; ++i) {
		if (visited[i]) continue;
		for (int j = 0; j < roadinfo[location][i].size(); ++j) {
			if (roadinfo[location][i][j].cost + cost > K) continue;
			//else if (roadinfo[location][i][j].cost + cost == K && i != N) continue;
			else if (roadinfo[location][i][j].length + curlength >= minlength[i][cost + roadinfo[location][i][j].cost]) continue;
			else if (roadinfo[location][i][j].length + curlength >= output) continue;
			else {
				visited[i] = 1;
				minlength[i][cost + roadinfo[location][i][j].cost] = roadinfo[location][i][j].length + curlength;
				minlength[i][cost + roadinfo[location][i][j].cost] = min(minlength[i][cost + roadinfo[location][i][j].cost], 
					minlength[location][cost] + roadinfo[location][i][j].length);
				dfs(i, roadinfo[location][i][j].cost + cost, roadinfo[location][i][j].length + curlength);
				visited[i] = 0;
			}
		}
	}
	return 0;
}
int main() {
	int S, D;
	road input;
	cin >> K >> N >> R;
	//memset(minlength, 1, sizeof(minlength));
	for (int i = 0; i < R; ++i) {
		cin >> S >> D;
		cin >> input.length >> input.cost;
		roadinfo[S][D].push_back(input);
	}
	for (int i = 0; i < MAXN; ++i) {
		for (int j = 0; j < MAXK; ++j) {
			minlength[i][j] = INF;
		}
	}
	minlength[1][0] = 0;
	visited[1] = 1;
	dfs(1, 0, 0);
	cout << (output == INF? -1: output) << endl;
	return 0;
}

其实写得有点啰嗦……
百练1190 生日蛋糕
这个是例题,主要的启示是可行性剪枝的预见性。还有搜索顺序从大到小,从下往上,便于可行性剪枝(这让我想起聚类和动规……自底向上好像都比自顶向下要效率高?应该这样可以排掉大量的可能性)。另外这个题目有很多细节上的处理要注意的。
首先是蛋糕的表面积,包括所有层的顶部露在外面的面积和侧面积,而前一部分直接就等于最底层的底面积。而且注意不要忘了加。
另外是从底层最大高度和半径开始枚举,设置的是int,注意这个是舍去小数部分的,因此要+1再开始枚举。
最后是还有一个可以改进的地方是记忆化搜索。
最后的AC代码(抄讲义的(捂脸)):

#include 
#include 
#include  
const int MAXN = 10000, MAXM = 20;
int N, M;
int minv[MAXM+1];
int mina[MAXM+1];
int maxv[MAXM+1][30][30];
int minarea = 0xffffff;
int area;
int calmaxv(int curdepth, int r, int h) {
	if (maxv[curdepth][r][h] != 0) return maxv[curdepth][r][h];
	else {
		int v = 0;
		for (int i = 0; i < curdepth; ++i) {
			v += (r - i) * (r - i) * (h - i);
		}
		maxv[curdepth][r][h] = v;
		return v;
	}
}
int dfs(int curdepth, int remv, int maxr, int maxh) {
	if (remv < 0) return 0;
	//printf("%d %d %d %d %d\n", curdepth, remv, maxr, maxh, area); 
	if (curdepth == 0) {
		//printf("yeah\n");
		if (remv == 0) {
			minarea = std::min(area, minarea);
			return 1;
		}
		else return 0;
	}
	if (minv[curdepth-1] > remv) return 0;
	if (mina[curdepth-1] + area >= minarea) return 0;
	if (calmaxv(curdepth, maxr, maxh) < remv) return 2;
	//printf("escape return\n");
	for (int r = maxr; r >= curdepth; --r) {
		if (curdepth == M) area = r*r; //别忘了底面积 
		for (int h = maxh; h >= curdepth; --h) {
			area += 2 * r * h;
			int x = dfs(curdepth - 1, remv - r*r*h, r-1, h-1);
			if (x == 2){
				area -= 2 * r * h;
				break;
			}
			else area -= 2 * r * h;
		}
	}
	return 0;
}
int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= M; ++i) {
		minv[i] = minv[i-1] + i*i*i;
		mina[i] = mina[i-1] + 2*i*i;
	}
	if (minv[M] > N) printf("0\n");
	else {
		int MAXR = sqrt(double(N-minv[M-1])/M) + 1;
		int MAXH = double(N-minv[M-1])/(M*M) + 1; //注意这里加一,因为直接舍掉了小数部分 
		//printf("%d %d\n", MAXR, MAXH);
		dfs(M, N, MAXR, MAXH);
		if (minarea == 0xffffff) printf("0\n");
		else printf("%d\n", minarea);
	}
	return 0;
}

海贼王之伟大航路
这个其实是生日蛋糕的变式题。那个可行性剪枝(就那个蛋糕里面最强的剪枝)我没想到,一开始想的一个作死的o(n^2)版本,试了好几次才幡然醒悟自己的愚蠢……这其实有点像启发式函数。最后我采取的策略是记录所有 的道路cost里面最小的那个,然后如果已经有的pathcost加上(N-depth)*mincost比最优解大就舍掉,事实证明尽管这个启发式函数相比蛋糕里面的相当之不靠谱,但是还是非常有用的。
最后AC代码:

#include 
#include 
#include 
using namespace std;
int minpath = 0xffffff;
bitset<16> vstd;
int N;
int minr = 0xffffff;
int route[16][16];
int dfs(int location, int path, int depth) {
	if (location == N-1) {
		if (depth != N-1) return 0;
		minpath = min(minpath, path);
		return 1;
	}
	vstd[location] = 1;
	if (path >= minpath) {
		vstd[location] = 0;
		return 0;
	}
	for (int i = 1; i < N; ++i) {
		if (vstd[i]) continue;
		if (depth != N-2 && i == N-1) continue;
		if (path + route[location][i] >= minpath) continue;
		if ((N-depth)*minr + path >= minpath) continue;
		dfs(i, path + route[location][i], depth + 1);
	}
	vstd[location] = 0;
	return 0;
}
int main() {
	scanf("%d", &N);
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < N; ++j) {
			scanf("%d", &route[i][j]);
			if (route[i][j] == 0) route[i][j] = 0xffffff; 
			if (minr > route[i][j]) minr = route[i][j];
		}
	}
	dfs(0, 0, 0);
	printf("%d\n", minpath);
	return 0;
}

(本来一开始还想尝试A搜索然后失败了,事实证明根本不懂A算法(捂脸),考完有空研究一下)
先这些吧。

你可能感兴趣的:(C++)