DFS, BFS, A*, 跳舞链
前言
没想到有HihoCoder这种网站是有教程的!
这次相关的解法看网站中的相关题目,懒得截图了 ^_^
搜索一·24点 - HihoCoder - 1304
思路
DFS求解24点
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 205;
const double inf = 1e8;
double a[4] = {0,0,0,0};
char ops[3] = {0,0,0};
char op_type[6] = {'+', '-',' * ','/','|','?'}; // | 是“反-”, ?是反“/”
double resCalc(double num1, double num2, char op){
if(op == '+') return num1 + num2;
else if(op == '-') return num1 - num2;
else if(op == '*') return num1 * num2;
else if(op == '/') return num1 / num2;
else if(op == '|') return num2 - num1;
else return num2 / num1;
}
// 计算在(((a ⊙ b) ⊙ c ) ⊙ d)形式下的值
bool calcType1(){
double res1 = resCalc(a[0], a[1], ops[0]);
double res2 = resCalc(res1, a[2], ops[1]);
double res3 = resCalc(res2, a[3], ops[2]);
return res3 == 24;
}
// 计算在((a ⊙ b) ⊙ (c ⊙ d))形式下的值
bool calcType2(){
double res1 = resCalc(a[0], a[1], ops[0]);
double res2 = resCalc(a[2], a[3], ops[2]);
double res3 = resCalc(res1, res2, ops[1]);
return res3 == 24;
}
bool dfsOps(int dpt){
if(dpt >= 3){
if(calcType1() || calcType2()) return true;
}else{
for(int i = 0; i < 6; i++){
ops[dpt] = op_type[i];
if(dfsOps(dpt + 1)) return true;
}
}
return false;
}
bool dfsNumber(){
sort(a, a + 4);
do{
if(dfsOps(0)) return true;
}while(next_permutation(a, a + 4));
return false;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
for(int i = 0; i < 4; i++) scanf("%lf", &a[i]);
puts(dfsNumber() ? "Yes" : "No");
}
}
搜索二·骑士问题 - HihoCoder - 1308
思路
BFS问题
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 205;
const int inf = 0x3f3f3f3f;
const int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
const int dy[] = {-1, 1, -2, 2, 2, -2, -1, 1};
struct node{
int x, y, val;
node(int px, int py, int pv): x(px), y(py), val(pv) {}
};
int d[3][8][8];
bool used[8][8];
queue<node> que;
void bfs(int k, int sx, int sy){
memset(used, false, sizeof(used));
que.push(node(sx, sy, 0));
while(!que.empty()){
node u = que.front();
que.pop();
if(used[u.x][u.y]) continue;
used[u.x][u.y] = true;
d[k][u.x][u.y] = u.val;
for(int i = 0; i < 8; i++){
node v(u.x + dx[i], u.y + dy[i], u.val + 1);
if(v.x < 0 || v.x >= 8 || v.y < 0 || v.y >= 8 || used[v.x][v.y]) continue;
que.push(v);
}
}
}
int main(){
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
for(int k = 0; k < 3; k++){
char ch, num;
cin >> ch >> num;
int x = ch - 'A';
int y = num - '1';
bfs(k, x, y); //BFS求每一个棋子到任意位置的最少步数
}
int ans = inf;
for(int i = 0; i < 8; i++){ //求到同一位置的最少步数
for(int j = 0; j < 8; j++){
ans = min(ans, d[0][i][j] + d[1][i][j] + d[2][i][j]);
}
}
cout << ans << endl;
}
}
搜索三·启发式搜索 - HihoCoder - 1312
思路
A*算法
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
#include <functional>
using namespace std;
typedef long long ll;
const int N = 9;
const int M = 4e5 + 15;
// 康托展开,从末位为第1位,首位为第n位
// X = a[n]*(n-1)! + a[n - 1]*(n-2)! + ... + a[1]*0!
struct node{
int x, as_val, val;
node(int px, int pasv, int pv): x(px), as_val(pasv), val(pv) {}
bool operator < (const node& b) const {
if(as_val != b.as_val) return as_val > b.as_val; //当前步数 + 曼哈顿距离作为首选
else return val > b.val; //当前步数作为次选
}
};
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int target[N] = {1,2,3,4,5,6,7,8,0};
priority_queue<node> que;
bool used[M];
inline int mabs(int x){ return x < 0 ? -x : x; }
inline void init(){
while(!que.empty()) que.pop();
memset(used, false, sizeof(used));
}
int cantor(int a[N]){
int sum = 0;
for(int i = 0; i < N; i++){
int cnt = 0;
for(int j = i + 1; j < N; j++){
if(a[j] < a[i]) cnt++; //后面的数比a[i]小的有几个
}
sum += cnt*fac[N - i - 1];
}
return sum;
}
void inv_cantor(int num, int a[]){
bool used[N + 1] = {0};
for(int i = 0; i < N; i++){
int cnt = num/fac[N - i - 1];
num %= fac[N - i - 1];
int j;
for(j = 0; j < N; j++){
if(!used[j]){
if(cnt == 0) break;
cnt--;
}
}
a[i] = j;
used[j] = true;
}
}
int getH(int u){ //求曼哈顿距离,作为估价函数
int sum = 0;
int a[N];
inv_cantor(u, a);
for(int i = 0; i < 9; i++){
int num = (a[i] - 1 + 9)%9;
int sx = i/3, sy = i%3;
int tx = num/3, ty = num%3;
sum += (mabs(sx - tx) + mabs(sy - ty));
}
return sum;
}
int bfs(int s, int t){
que.push(node(s, getH(s), 0));
while(!que.empty()){
node nd = que.top();
int x = nd.x;
que.pop();
if(used[x] == true) continue;
used[x] = true;
if(x == t) return nd.val;
int a[N];
inv_cantor(x, a);
int xpos = 0;
while(a[xpos]) xpos++;
int i = xpos/3;
int j = xpos%3;
for(int dir = 0; dir < 4; dir++){
int newi = i + dx[dir];
int newj = j + dy[dir];
if(newi < 0 || newi >= 3 || newj < 0 || newj >= 3) continue;
int newxpos = newi*3 + newj;
swap(a[xpos], a[newxpos]);
int newx = cantor(a);
if(!used[newx]){
que.push(node(newx, nd.val + getH(newx) + 1, nd.val + 1));
}
swap(a[xpos], a[newxpos]);
}
}
return -1;
}
int main(){
int t;
scanf("%d", &t);
int e = cantor(target);
while(t--){
init();
int a[N];
for(int i = 0; i < N; i++){ scanf("%d", &a[i]); }
int s = cantor(a);
int ans = bfs(s, e);
if(ans == -1){
puts("No Solution!");
}else{
printf("%d\n", ans);
}
}
}
搜索四·跳舞链 - HihoCoder - 1317
思路
跳舞链基本问题
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 10005;
int tot;
int posx[N], posy[N]; //Node对应的posx和posy
int uu[N], dd[N], ll[N], rr[N]; //Node的上下左右指针
int row_id[N]; //每一行第一个Node的id
int col_cnt[N]; //每一列的Node个数
inline void init(int m){
tot = m + 1;
memset(row_id, -1, sizeof(row_id)); //初始化每一行第一个元素的id为-1
for(int i = 0; i <= m; i++){ //初始化列节点和Head节点,做好链接
col_cnt[i] = 0;
ll[i] = (i - 1 >= 0 ? i - 1 : m);
rr[i] = (i + 1 <= m ? i + 1 : 0);
uu[i] = dd[i] = i;
}
}
void link(int x, int y){
int id = tot;
posx[id] = x, posy[id] = y; //记录该节点位于哪一行哪一列
uu[id] = uu[y]; //做好链接
dd[id] = y;
uu[y] = id;
dd[uu[id]] = id;
if(row_id[x] == -1){
row_id[x] = ll[id] = rr[id] = id;
}else{
ll[id] = ll[row_id[x]];
rr[id] = row_id[x];
rr[ll[id]] = id;
ll[row_id[x]] = id;
}
col_cnt[y]++;
tot++;
}
void remove(int y){ //删除这一列(包括列节点),和列上有元素所在的行
ll[rr[y]] = ll[y]; //对于列节点,只需使其左右节点不指向它,自身左右不修改,便于恢复
rr[ll[y]] = rr[y];
for(int idj = dd[y]; idj != y; idj = dd[idj]){
for(int idi = rr[idj]; idi != idj; idi = rr[idi]){
uu[dd[idi]] = uu[idi]; //对于其中的节点,只需使其上下节点不指向它
dd[uu[idi]] = dd[idi]; //因为扫描是从列开始扫描的
col_cnt[posy[idi]]--;
}
}
}
void resume(int y){ //模拟递归时的恢复现场
for(int idj = uu[y]; idj != y; idj = uu[idj]){
for(int idi = ll[idj]; idi != idj; idi = ll[idi]){
uu[dd[idi]] = idi;
dd[uu[idi]] = idi;
col_cnt[posy[idi]]++;
}
}
ll[rr[y]] = y;
rr[ll[y]] = y;
}
bool dance(){
if(rr[0] == 0) return true;
int c = rr[0];
for(int id = rr[0]; id != 0; id = rr[id]){ //选择其中节点最少的列
if(col_cnt[id] < col_cnt[c]) c = id;
}
remove(c);
for(int idj = dd[c]; idj != c; idj = dd[idj]){
for(int idi = rr[idj]; idi != idj; idi = rr[idi]) remove(posy[idi]);
if(dance()) return true;
for(int idi = ll[idj]; idi != idj; idi = ll[idi]) resume(posy[idi]);
}
resume(c);
return false;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
int n, m;
scanf("%d%d", &n, &m);
init(m);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
int tmp;
scanf("%d", &tmp);
if(tmp){
link(i, j);
}
}
}
puts(dance() ? "Yes" : "No");
}
}
搜索五·数独 - HihoCoder - 1321
思路
跳舞链解数独
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>
using namespace std;
const int N = 4e5 + 15;
int tot;
int posx[N], posy[N]; //Node对应的posx和posy
int uu[N], dd[N], ll[N], rr[N]; //Node的上下左右指针
int row_id[N]; //每一行第一个Node的id
int col_cnt[N]; //每一列的Node个数
int mat[800][400];
int G[10][10];
stack<int> stk;
void setMat(int i, int j, int k){
int id = (i - 1) * 9 + j; // 表示第i行第j列格子的编号
int pid = (id - 1) * 9 + k; // 表示该格子填写k所对应的方案编号
// 约束条件1 - 对应第1~81列
// 第(i-1) * 9+k列表示第i行存在数字k
mat[pid][(i - 1) * 9 + k] = 1;
// 约束条件2 - 对应第82~162列
// 第81+(j-1) * 9+k列表示第j列存在数字k
mat[pid][81 + (j - 1) * 9 + k] = 1;
// 约束条件3 - 对应第163~243列
// 第162+(t-1) * 9+k列表示第t个九宫存在数字k
int t = ((i - 1) / 3 * 3 + (j - 1) / 3) + 1;
mat[pid][162 + (t - 1) * 9 + k] = 1;
// 约束条件4 - 对应第244~324列
// 第243+id列表示第i行第j列填写有数字
mat[pid][243 + id] = 1;
}
inline void init(){
int m = 324;
while(!stk.empty()) stk.pop();
tot = m + 1;
memset(row_id, -1, sizeof(row_id)); //初始化每一行第一个元素的id为-1
for(int i = 0; i <= m; i++){ //初始化列节点和Head节点,做好链接
col_cnt[i] = 0;
ll[i] = (i - 1 >= 0 ? i - 1 : m);
rr[i] = (i + 1 <= m ? i + 1 : 0);
uu[i] = dd[i] = i;
}
}
void link(int x, int y){
int id = tot;
posx[id] = x, posy[id] = y; //记录该节点位于哪一行哪一列
uu[id] = uu[y]; //做好链接
dd[id] = y;
uu[y] = id;
dd[uu[id]] = id;
if(row_id[x] == -1){
row_id[x] = ll[id] = rr[id] = id;
}else{
ll[id] = ll[row_id[x]];
rr[id] = row_id[x];
rr[ll[id]] = id;
ll[row_id[x]] = id;
}
col_cnt[y]++;
tot++;
}
void createLinks(){
for(int i = 1; i < 800; i++){ //根据mat矩阵的结果链接节点
for(int j = 1; j < 400; j++){
if(mat[i][j]){
link(i, j);
}
}
}
}
void remove(int y){ //删除这一列(包括列节点),和列上有元素所在的行
ll[rr[y]] = ll[y]; //对于列节点,只需使其左右节点不指向它,自身左右不修改,便于恢复
rr[ll[y]] = rr[y];
for(int idj = dd[y]; idj != y; idj = dd[idj]){
for(int idi = rr[idj]; idi != idj; idi = rr[idi]){
uu[dd[idi]] = uu[idi]; //对于其中的节点,只需使其上下节点不指向它
dd[uu[idi]] = dd[idi]; //因为扫描是从列开始扫描的
col_cnt[posy[idi]]--;
}
}
}
void resume(int y){ //模拟递归时的恢复现场
for(int idj = uu[y]; idj != y; idj = uu[idj]){
for(int idi = ll[idj]; idi != idj; idi = ll[idi]){
uu[dd[idi]] = idi;
dd[uu[idi]] = idi;
col_cnt[posy[idi]]++;
}
}
ll[rr[y]] = y;
rr[ll[y]] = y;
}
bool dance(){
if(rr[0] == 0) return true;
int c = rr[0];
for(int id = rr[0]; id != 0; id = rr[id]){ //选择其中节点最少的列
if(col_cnt[id] < col_cnt[c]) c = id;
}
remove(c);
for(int idj = dd[c]; idj != c; idj = dd[idj]){
for(int idi = rr[idj]; idi != idj; idi = rr[idi]) remove(posy[idi]);
stk.push(posx[idj]);
if(dance()) return true;
for(int idi = ll[idj]; idi != idj; idi = ll[idi]) resume(posy[idi]);
stk.pop();
}
resume(c);
return false;
}
void solve(){
init();
createLinks();
dance();
while(!stk.empty()){
int x, y, k;
int pid = stk.top();
stk.pop();
for(int i = 1; i <= 81; i++){
if(mat[pid][i]){ //根据选择方案推算出第几行第几列填了几
x = (i - 1)/9 + 1;
k = i%9;
break;
}
}
for(int i = 82; i <= 162; i++){
if(mat[pid][i]){
y = (i - 82)/9 + 1;
break;
}
}
G[x][y] = (k ? k : 9);
}
for(int i = 1; i <= 9; i++){
for(int j = 1; j <= 9; j++){
printf("%d", G[i][j]);
if(j < 9) putchar(' ');
}
puts("");
}
}
int main(){
int t;
scanf("%d", &t);
while(t--){
memset(mat[0], 0, sizeof(mat));
for(int i = 1; i <= 9; i++){
for(int j = 1; j <= 9; j++){
scanf("%d", &G[i][j]);
if(G[i][j]){
setMat(i, j, G[i][j]);
}else{
for(int k = 1; k <= 9; k++){
setMat(i, j, k);
}
}
}
}
solve();
}
}