kings(骑士)
Time Limit5000 ms Memory Limit131072 KBytes
Description
用字符矩阵来表示一个8x8的棋盘,'.'表示是空格,'P'表示人质,'K'表示骑士。 每一步,骑士可以移动到他周围的8个方格中的任意一格。如果你移动到的格子中有人质(即'P'),你将俘获他。但不能移到出棋盘或当前是'K'的格子中。 请问最少要移动多少步骑士才能俘获所有的人质。
Input Format
第一行一个整数N(<=5),表示有多少个棋盘。即多组测试数据。 每一组有8行,每行8个字符。字符只有'.',大写'P',大写'K'三种字符。'P'和'K'的个数范围都在[1,10]。
Output Format
有N行,每行只一个整数,相应棋盘俘获全部人质所需要的最少步数。
Sample Input
2
P......P
........
........
........
...KK...
........
........
P......P
.....P.P
..K....P
....K...
..PP...P
...K..KK
........
K.......
KP.K....
Sample Output
20
9
Solution
分析:1.p,k<=10考虑用状压
2.每个骑士的路径独立互不影响(若路径相交必然不是最优解)
基于以上两点,可以先求出每个骑士拯救集合S(状压2进制表示)的最小步数,然后通过这个求出前i个骑士拯救集合S的最小步数
#include<cstdio> #include<cstring> #include<algorithm> #define INF 0x7f7f7f7f using namespace std; int T,p[11],k[11],d[65][65]; int f1[11][11][1<<10],f2[11][1<<10]; void ycl(){ for (int xa=1;xa<=8;++xa) for (int ya=1;ya<=8;++ya) for (int xb=1;xb<=8;++xb) for (int yb=1;yb<=8;++yb){ int x=(xa-1)*8+ya,y=(xb-1)*8+yb; int dex=abs(xa-xb),dey=abs(ya-yb); int t=min(dex,dey); d[x][y]=t+(dex-t)+(dey-t); } } int main(){ ycl(); for (scanf("%d",&T);T;--T){ memset(f1,0x7f,sizeof f1); memset(f2,0x7f,sizeof f2); p[0]=k[0]=0; for (int i=1;i<=8;++i) for (int j=1;j<=8;++j){ char c=getchar(); while (c!='.'&&c!='P'&&c!='K') c=getchar(); if (c=='P') p[++p[0]]=(i-1)*8+j; if (c=='K') k[++k[0]]=(i-1)*8+j; } for (int i=1;i<=k[0];++i){ for (int j=1;j<=p[0];++j) f1[i][j][1<<(j-1)]=d[k[i]][p[j]]; for (int s=1;s<(1<<p[0]);++s) for (int j=1;j<=p[0];++j) if (s&(1<<(j-1))) for (int l=1;l<=p[0];++l) if (!(s&(1<<(l-1)))) f1[i][l][s|(1<<(l-1))]=min(f1[i][l][s|(1<<(l-1))],f1[i][j][s]+d[p[j]][p[l]]); } for (int i=1;i<=k[0];++i) for (int s=1;s<(1<<p[0]);++s) for (int j=1;j<=p[0];++j) f1[i][0][s]=min(f1[i][0][s],f1[i][j][s]); f2[0][0]=0; for (int i=1;i<=k[0];++i){ f2[i][0]=0; for (int s=1;s<(1<<p[0]);++s){ f2[i][s]=f2[i-1][s]; for (int ss=0;ss<s;++ss) if ((s|ss)==s&&(f2[i-1][ss]!=INF&&f1[i][0][s^ss]!=INF)) f2[i][s]=std::min(f2[i][s],f2[i-1][ss]+f1[i][0][s^ss]); } } printf("%d\n",f2[k[0]][(1<<p[0])-1]); } }
以上是标解,下面提供一种(蒟蒻)的做法
我们发现每个骑士只有两种状态:1.在初始位置 2.在某个人质的位置上
每个人质有三种状态:1.没被拯救 2.已经被拯救 3.被拯救且有骑士在这个位置
所以 用f[s1][s2]表示人质状态为s1,骑士的状态为s2的最少步数
每次转移可以将s1中的骑士走出去拯救人质或将s2中在初始位置的骑士走出去拯救人质
#include<cstdio> #include<cstring> #include<algorithm> short int o3[2][59050]; short int p[11],k[11]; short int T,A,B,ans,pw[10],d[65][65],f[1<<10][59050]; inline void dfs(int s1,int s2){ if (f[s1][s2]+(p[0]-o3[1][s2]-o3[0][s2])>=ans) return; if (o3[1][s2]+o3[0][s2]==p[0]){ ans=std::min(ans,f[s1][s2]); return; } for (int i=0;i<p[0];++i) if (!((s2/pw[i])%3)){ for (int j=0;j<p[0];++j) if (((s2/pw[j])%3)==2){ int t1=s1,t2=s2-pw[j]+pw[i]*2; if (f[t1][t2]>f[s1][s2]+d[p[j+1]][p[i+1]]) f[t1][t2]=f[s1][s2]+d[p[j+1]][p[i+1]],dfs(t1,t2);; } for (int j=0;j<k[0];++j) if (s1&(1<<j)){ int t1=s1^(1<<j),t2=s2+pw[i]*2; if (f[t1][t2]>f[s1][s2]+d[k[j+1]][p[i+1]]) f[t1][t2]=f[s1][s2]+d[k[j+1]][p[i+1]],dfs(t1,t2); } } } inline void csh(){ memset(f,0x7f,sizeof f); p[0]=k[0]=0; for (int i=1;i<=8;++i) for (int j=1;j<=8;++j){ char c=getchar(); while (c!='.'&&c!='P'&&c!='K') c=getchar(); if (c=='P') p[++p[0]]=(i-1)*8+j; if (c=='K') k[++k[0]]=(i-1)*8+j; } A=(1<<(k[0]))-1; f[A][0]=0; ans=d[k[1]][p[1]]; for (int i=2;i<=p[0];++i) ans+=d[p[i-1]][p[i]]; } inline void ycl(){ for (int xa=1;xa<=8;++xa) for (int ya=1;ya<=8;++ya) for (int xb=1;xb<=8;++xb) for (int yb=1;yb<=8;++yb){ int x=(xa-1)*8+ya,y=(xb-1)*8+yb,t; int dex=std::abs(xa-xb),dey=std::abs(ya-yb); t=std::min(dex,dey); d[x][y]=d[y][x]=t+(dex-t)+(dey-t); } int maxs=0; for(int i=0;i<10;++i) maxs+=pw[i]*2; for (int i=0;i<=maxs;++i) for (int j=0;j<10;++j) if ((i/pw[j])%3) ++o3[(i/pw[j])%3-1][i]; } int main(){ pw[0]=1; for (int i=1;i<10;++i) pw[i]=pw[i-1]*3; ycl(); for (scanf("%d",&T);T;--T){ csh(); dfs(A,0); printf("%d\n",ans); } }