Loading

2022CCPC威海 D. Sternhalma(记忆化搜索/状压)

题意大概是给定一个19个格子的六边形棋盘,每个位置有一个分数,每次操作可以拿走一个棋子(不得分)或者将当前棋子跳过相邻的一个棋子(得分为跳过的棋子所在位置的分数)且将跳过的棋子拿走,问分数最大是多少。

记忆化搜索+状压。因为只有19个位置,因此可以用一个int整数表示状态,同时注意到是一个棋盘对应多种摆放顺序,因此联想到记忆化。这个题的难点在于棋盘是六边形,枚举转移较为麻烦。

#include <bits/stdc++.h>
using namespace std;
int score[5][5], n;
int vis[(1 << 20) + 5];
int Pos[5][5] = {//Pos[i][j]表示i, j这个位置对应在状态中的下标
	{0, 1, 2},
	{3, 4, 5, 6},
	{7, 8, 9, 10, 11},
	{12, 13, 14, 15},
	{16, 17, 18},
};
int d1[5][6][2] = {//d1[i]表示第i行的位置往六个方向移动一格的坐标增量
	{
		{-1, -1}, {-1, 0}, {0, -1}, {0, 1}, {1, 0}, {1, 1},
	},
	{
		{-1, -1}, {-1, 0}, {0, -1}, {0, 1}, {1, 0}, {1, 1},
	},
	{
		{-1, -1}, {-1, 0}, {0, -1}, {0, 1}, {1, -1}, {1, 0},
	},
	{
		{-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0},
	},
	{	
		{-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0},
	}
};
int d2[5][6][2] = {//d2[i]表示第i行的位置往六个方向移动两格的坐标增量
	{
		{-2, -2}, {-2, 0}, {0, -2}, {0, 2}, {2, 0}, {2, 2},
	},
	{
		{-2, -2}, {-2, 0}, {0, -2}, {0, 2}, {2, -1}, {2, 1},
	},
	{
		{-2, -2}, {-2, 0}, {0, -2}, {0, 2}, {2, -2}, {2, 0},
	},
	{
		{-2, -1}, {-2, 1}, {0, -2}, {0, 2}, {2, -2}, {2, 0},
	},
	{	
		{-2, 0}, {-2, 2}, {0, -2}, {0, 2}, {2, -2}, {2, 0},
	}
};
int limit[5] = {2, 3, 4, 3, 2};//每行数的个数
int getStat(string s) {
	int ans = 0;
	for(int i = 0; i < s.size(); i++) {
		if(s[i] == '#') {
			ans |= (1 << i);
		}
	}
	return ans;
}
int dfs(int stat, int step) {
	if(vis[stat] != -0x3f3f3f3f) return vis[stat];
	int ans = 0;
	int mp[5][5];
	for(int i = 0; i < 5; i++) {
		for(int j = 0; j < 5; j++) {
			mp[i][j] = 0;
		}
	}
	int one = 0;
	for(int i = 0; i < 19; i++) {
		if((stat >> i) & 1) {
			//第一种操作
			one++;
			ans = max(ans, dfs(stat & (~(1 << i)), step));
			if(i >= 0 && i <= 2) {
				mp[0][i] = 1;
			} else if(i >= 3 && i <= 6) {
				mp[1][i - 3] = 1;
			} else if(i >= 7 && i <= 11) {
				mp[2][i - 7] = 1;
			} else if(i >= 12 && i <= 15) {
				mp[3][i - 12] = 1;
			} else {
				mp[4][i - 16] = 1;
			}
		}
	}
	for(int i = 0; i < 5; i++) {
		for(int j = 0; j <= limit[i]; j++) {
			if(!mp[i][j]) continue;
			int nowx = i, nowy = j;
			for(int k = 0; k < 6; k++) {
				int nx1 = nowx + d1[i][k][0], ny1 = nowy + d1[i][k][1];//要跨过的点
				int nx2 = nowx + d2[i][k][0], ny2 = nowy + d2[i][k][1];//落点
				if(nx1 < 0 || nx1 >= 5 || ny1 < 0 || ny1 > limit[nx1]) continue;//短路 所以不会出现nx1越界的情况
				if(nx2 < 0 || nx2 >= 5 || ny2 < 0 || ny2 > limit[nx2]) continue;
				if(!mp[nx1][ny1]) continue;
				if(mp[nx2][ny2]) continue;
				int nxt_stat = stat;
				nxt_stat &= (~(1 << Pos[nowx][nowy]));//构造下一个状态 首先清除当前位置
				nxt_stat &= (~(1 << Pos[nx1][ny1]));//清除跳过的位置
				nxt_stat |= (1 << (Pos[nx2][ny2]));//跳到的位置设为1
				ans = max(ans, score[nx1][ny1] + dfs(nxt_stat, step + 1));
			}
		}
	}
	vis[stat] = ans;
	return ans;
}
int main() {
	for(int i = 0; i < 5; i++) {
		for(int j = 0; j <= limit[i]; j++) {
			cin >> score[i][j];
		}
	}
	cin >> n;
	for(int i = 0; i < (1 << 20); i++) {
		vis[i] = -0x3f3f3f3f;
	}
	for(int i = 1; i <= n; i++) {
		string s = "";
		for(int j = 1; j <= 5; j++) {
			string tmp;
			cin >> tmp;
			s += tmp;
		}
		cout << dfs(getStat(s), 1) << endl;
	}
	return 0;
}
posted @ 2022-11-10 19:39  脂环  阅读(95)  评论(0编辑  收藏  举报