【cofun1376】kings
Description
用字符矩阵来表示一个8x8的棋盘,’.’表示是空格,’P’表示人质,’K’表示骑士。 每一步,骑士可以移动到他周围的8个方格中的任意一格。如果你移动到的格子中有人质(即’P’),你将俘获他。但不能移到出棋盘或当前是’K’的格子中。 请问最少要移动多少步骑士才能俘获所有的人质。
Input Format
第一行一个整数N(<=5),表示有多少个棋盘。即多组测试数据。 每一组有8行,每行8个字符。字符只有’.’,大写’P’,大写’K’三种字符。’P’和’K’的个数范围都在[1,10]。
Output Format
有N行,每行只一个整数,相应棋盘俘获全部人质所需要的最少步数。
Sample Input
样例1输入
1
.PPPPKP.
……..
……..
……..
……..
……..
……..
……..
样例2输入
2
P……P
……..
……..
……..
…KK…
……..
……..
P……P
…..P.P
..K….P
….K…
..PP…P
…K..KK
……..
K…….
KP.K….
Sample Output
样例1输出
6
样例2输出
20
9
分析:
一个神奇的DP题目。
人质数,骑士数 <= 10,考虑状压DP。- 由于骑士可以移动到周围的8个方格,所以可以直接计算出人质与人质,骑士与人质之间的步数。伪代码:
d[i][j] = max(fabs(x[i] - x[j]), fabs(y[i] - y[j]));
- 第一步状压:
计算从每个人质出发到达其他所有人质需要的最小步数 。
f1[i][l] = min(f1[i][l], f1[j][l - (1 << i)] + disp[i][j]);
f1[i][j]:从第i个人质出发到达人质情况为j(把到达人质情况压进二进制数j,0:已到达/ 1:未到达) - 第二步状压:
计算从每个骑士出发到达所有人质需要的最小步数 。
f2[i][l] = min(f2[i][l], f1[j][l] + disk[i][j]);
f2[i][j]:从第i个骑士出发到达人质情况为j(同上) - 第三步状压:
计算前i个骑士到达所有人质需要的最小步数。
f3[i][j] = min(f3[i][j], f3[i - 1][j - l] + f2[i][l]);
f3[i][j]:到第i个骑士为止所到达人质情况为j(同上)
- 由于骑士可以移动到周围的8个方格,所以可以直接计算出人质与人质,骑士与人质之间的步数。伪代码:
【那会儿琢磨了好久QAQ果然分析一下就很清楚了呢。
- 代码:
#include <bits/stdc++.h>
using namespace std;
struct info{
int x, y;
}p[15], k[15];
int N, i, j, l, np, nk, t, disp[15][15], disk[15][15], f1[15][1 << 15], f2[15][1 << 15], f3[15][1 << 15];
char c;
int main()
{
for(scanf("%d\n", &N); N; N --)
{
np = nk = 0;
memset(p, 0, sizeof(p));
memset(k, 0, sizeof(k));
memset(disp, 0, sizeof(disp));
memset(disk, 0, sizeof(disk));
memset(f1, 0x3f, sizeof(f1));
memset(f2, 0x3f, sizeof(f2));
memset(f3, 0x3f, sizeof(f3));
//初始化
for(i = 1; i <= 8; i ++)
{
for(j = 1; j <= 8; j ++)
{
scanf("%c", &c);
if (c == 'P')
p[np].x = i, p[np ++].y = j;
if (c == 'K')
k[nk].x = i, k[nk ++].y = j;
}
scanf("\n");
}
//读入数据
for(i = 0; i < np; i ++)
for(j = i + 1; j < np; j ++)
disp[i][j] = disp[j][i] = max(fabs(p[i].x - p[j].x), fabs(p[i].y - p[j].y));
for(i = 0; i < nk; i ++)
for(j = 0; j < np; j ++)
disk[i][j] = max(fabs(k[i].x - p[j].x), fabs(k[i].y - p[j].y));
//计算P&P之间、K&P之间的距离,即互相到达的最小步数
for(i = 0; i < np; i ++)
f1[i][1 << i] = f1[i][0] = 0;
for(l = 1; l < (1 << np); l ++)
for(i = 0; i < np && (1 << i) <= l; i ++)
if ((1 << i) & l)
for(j = 0; j < np && (1 << j) <= l; j ++)
if ((i != j) && ((1 << j) & l))
f1[i][l] = min(f1[i][l], f1[j][l - (1 << i)] + disp[i][j]);
//第一步动规:计算从每个人质出发到达其他所有人质需要的最小步数
for(i = 0; i < nk; i ++)
f2[i][0] = 0;
for(i = 0; i < nk; i ++)
for(l = 1; l < (1 << np); l ++)
for(j = 0; j < np && (1 << j) <= l; j ++)
if ((1 << j) & l)
f2[i][l] = min(f2[i][l], f1[j][l] + disk[i][j]);
//第二步动规:计算从每个骑士出发到达所有人质需要的最小步数
for(i = 0; i < (1 << np); i ++)
f3[0][i] = f2[0][i];
for(i = 0; i < nk; i ++)
f3[i][0] = 0;
for(i = 1; i < nk; i ++)
for(j = 1; j < (1 << np); j ++)
for(l = 0; l <= j; l ++)
if ((l & j) == l)
f3[i][j] = min(f3[i][j], f3[i - 1][j - l] + f2[i][l]);
//第三步动规:计算前i个骑士到达所有人质需要的最小步数
printf("%d\n", f3[nk - 1][(1 << np) - 1]);
//输出答案
}
return 0;
}
- 打完这题后也调了蛮久,自己写了个小总结,顺便放上来:
总结:
- 注意数组范围是从1还是0开始以及结束是否为个数减一。
- 动规按照方程顺序循环,不要混乱。
- 注意初始化 ,比如这题初始化二进制0,状压从1开始循环。
- 保持思路清晰。
食堂的鸡腿儿真的很治愈人哇!!太好吃辣!
= =打完第二题后忍不住犯困了一会,果然我不适合修仙TUT