感觉还是比动规简单一些的……但是最近脑子有点糊。
首先是要注意邻接表是有两种的,在稀疏图中,最好用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算法(捂脸),考完有空研究一下)
先这些吧。