[胡泽聪 趣题选讲]大包子环绕宝藏-[状压dp]
Description
你有一个长方形的地图,每一个格子要么是一个障碍物,要么是一个有一定价值的宝藏,要么是一个炸弹,或者是一块空地。你的初始位置已经给出。
你每次可以走到上、下、左、右这四个相邻的格子。你不允许走出这幅地图,不允许进入有宝藏、障碍物或是炸弹的地方。你需要规划一个闭合的路线(起点和终点都必须在初始位置)来取得宝藏。注意这个路线围成的多边形中不可以包含炸弹。假设路线围成的多边形包含的所有宝藏的价值之和为v,并且你从起点到终点走了 k步(从一个格子走到旁边的格子算作一步),那么你沿该路线走一次将可以获得v-k的利润。
你的任务是规划一个不包含炸弹的闭合路线,并可获得最大的利润。
注意路线可以自交。为了确定一个格子是否在这条路线里面,请使用以下算法判断:
1.假设该点的坐标为需要判断的点为 p(i,j) ,该点不在路线上
2.从该点往任意方向作一条射线,如果与路线相交奇数次,我们就认为这个格子在这条路线里面,否则这个格子在这条路线外面。
n,m<=20。炸弹和宝藏的个数总和不超过8个,保证只有1个初始点。
Solution
本题难点其实就是判断格子是否在路线里面。(题目好良心系列)我选定的射线方向是竖直向上(当然其他方向也ok呀)。
所以,如果画出路径,所有的射线只会和横向路径相交(竖向的路径就直接忽略啦)
对于每一小段横向路径(即点(x,y)到(x,y+1)),我们记录这一小段路径的右端点。如此以保证奇偶性正确。
5 — 4 —3
| |
6 9 2
| |
7 — 8 — 1
如图,我们沿1-8寻找回路,则被记录的点有3,4,8,1。
虽然5和7没有被记录到,但这不影响9(多边形内部)和多边形外部的点的判断。
(当然,如果是记录所有横向路径的点也可以,例如记3,4,5,7,8,1;不过这就需要加些特判了)
Code
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int N=21,K=8; int f[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; int tx[10],ty[10],cnt,k; int n,m,sx,sy,w[10]; char mp[30][30]; int dp[23][23][1<<K]; int q[21*21*(1<<K)],l,r; int num(int x,int y,int z){return x*N*(1<<K)+y*(1<<K)+z;} int getx(int c){return c/(N*(1<<K));} int gety(int c){return c/(1<<K)%N;} int getz(int c){return c%(1<<K);} int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) { scanf("%s",mp[i]+1); for (int j=1;j<=m;j++) if (mp[i][j]=='S'){mp[i][j]='.';sx=i;sy=j;} else if (mp[i][j]>'0'&&mp[i][j]<'9') {tx[mp[i][j]-'1']=i;ty[mp[i][j]-'1']=j;cnt++;mp[i][j]='#';} } k=cnt; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (mp[i][j]=='B') {mp[i][j]='#';tx[cnt]=i;ty[cnt]=j;cnt++;} for (int i=0;i<k;i++) scanf("%d",&w[i]); memset(dp,0x3f,sizeof(dp)); dp[sx][sy][0]=0; q[1]=num(sx,sy,0); l=r=1; int _x,_y,_z,zz; while (l<=r) { _x=getx(q[l]);_y=gety(q[l]);_z=getz(q[l]);l++; for (int i=0;i<4;i++) { if (_x+f[i][0]<=n&&_x+f[i][0]&&_y+f[i][1]&&_y+f[i][1]<=m&&mp[_x+f[i][0]][_y+f[i][1]]=='.') { zz=_z; if (!i) for (int j=0;j<cnt;j++) if (tx[j]>_x&&ty[j]==_y) zz^=1<<j; if (i==1) for (int j=0;j<cnt;j++) if (tx[j]>_x+f[i][0]&&ty[j]==_y+f[i][1]) zz^=1<<j; if (dp[_x][_y][_z]+1<dp[_x+f[i][0]][_y+f[i][1]][zz]) { dp[_x+f[i][0]][_y+f[i][1]][zz]=dp[_x][_y][_z]+1; q[++r]=num(_x+f[i][0],_y+f[i][1],zz); } } } } bool _is;int ans=0,sum,t; for (int i=0;i<1<<cnt;i++) { sum=0;_is=1; for (int j=0;j<cnt;j++) { t=i&(1<<j); if (j>=k&&t) _is=0; if (j<k&&t) sum+=w[j]; } if (_is) ans=max(ans,sum-dp[sx][sy][i]); } cout<<ans; }