SCOI2009 围豆豆
题目链接https://www.luogu.com.cn/problem/P2566
分析
这道题看着前边的叙述好像不是很难,不就是把豆子围起来吗,一看数据,\(状压DP\)肯定是了,状压\(DP\)的本质其实还是暴力,既然我们已经选择了一个暴力,不如暴力到底,于是我开始枚举围成的路径,打了一堆写好后发现好像不对,(我最开始以为路径一定是矩形),然后.....路径所形成的多边形(可能是含自交的复杂多边形),所以枚举是不现实的了,这里就有一个很神奇的办法,射线法判断点是不是在多边形内,具体看链接::https://blog.csdn.net/qq_27161673/article/details/52973866,一句话总结就是引一条切线,如果与多边形有偶数个交点,则在外部,奇数个则在内部,但你会发现当点在多边形的边上时这个是不成立的,所以我们还需要做点事情,当然根据那个链接里边的做也行,不过好像过于繁琐,我们只需要将这条边上开下闭即可,这样就不会出问题了。转移的时候怎么转移呢?
因为涉及到每个豆子,并且豆子和豆子都不一样,所以坐标就直接会占二维。状压\(DP\)的话还要额外再加一层状态,就是每个豆子放与不放,对应的二进制位为1则放否则不放。这样就确定好了状态,下面我们需要跑一条回路出来,我感觉没有比\(SPFA\)更合适的了,从每一个可以被围起来的点开始跑,一直跑到队列为空,那可能会死循环吗?显然不会,因为跑着跑着步数就变多了,然后就不会转移,然后就不会入队列了,所以不会死循环。
大概思路是有了,下面考虑一些细节的东西。
怎么判断是交了奇数次还是偶数次呢?有一个东西叫异或,很好用,之前写NOI ONLINE的一道题时也用过,对于当前状态,假如该路径经过了豆子引出的射线,就把该二进制位异或1,如果它原来是1,就是曾经经过,那么再次经过就应该是偶数次了,异或1后是0,考虑围住的豆子时就应该把这个减去,emm好像还不是很清楚,就是我当前的\(DP\)数组加上了这个豆子,但是我当前的状态并没有,所以转移的时候一定要把这颗豆子减去,反之则加上,我感觉这么做好像写起来会简单一些。
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=15,M=12;
int val[M],dp[N][N][1<<M],sum[1<<M],n,m,num;;
int g[N][N],ans;
bool vis[N][N][1<<M];
struct Node{
int x,y,zt;
Node(){}
Node(int a,int b,int c){
x=a;y=b;zt=c;
}
}bea[M];
const int dx[4]={0,0,-1,1};
const int dy[4]={-1,1,0,0};
void spfa(int sx,int sy){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<1<<num;k++)
dp[i][j][k]=-0x3f3f3f3f;
queue<Node > q;
q.push(Node(sx,sy,0));
dp[sx][sy][0]=0;
vis[sx][sy][0]=1;
while(!q.empty()){
Node u=q.front();q.pop();
vis[u.x][u.y][u.zt]=0;
if(u.x==sx&&u.y==sy)
ans=max(ans,dp[u.x][u.y][u.zt]);
for(int k=0;k<4;k++){
int x=u.x+dx[k],y=u.y+dy[k];
int sum=0;
if (x<=0||x>n||y<=0||y>m||g[x][y]) continue;
int zt=u.zt,yy=max(y,u.y);
if(k<=1)
for(int i=1;i<=num;i++)
if(bea[i].y==yy&&bea[i].x<x){
zt^=1<<i-1;
if(zt>>i-1&1)sum+=val[i];
else sum-=val[i];
}
if(dp[x][y][zt]<dp[u.x][u.y][u.zt]+sum-1){
dp[x][y][zt]=dp[u.x][u.y][u.zt]+sum-1;
if(!vis[x][y][zt])vis[x][y][zt]=1,q.push(Node(x,y,zt));
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&num);
for(int i=1;i<=num;i++)scanf("%d",&val[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='#')ch=getchar();
if(ch=='#')g[i][j]=-1;
else g[i][j]=ch-'0';
if(g[i][j]>=1)bea[g[i][j]].x=i,bea[g[i][j]].y=j;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(!g[i][j])spfa(i,j);
printf("%d\n",ans);
}