洛谷 P2566 [SCOI2009]围豆豆【状压】【dp】


题目:

传送门


题意:

在一张 n ∗ m n*m nm的图上有 d d d颗豆子,各有一个分数,我们可以选择任意点作为起点,向上下左右进行划范围,豆子和障碍是不允许被划在边界处,当划回起点的时候开始算分
此时的得分等于被划出来的封闭的多边形圈住的豆子的得分和 − - 划范围所用的步数
问最大得分是多少


分析:

豆子最多只能有 9 9 9颗,而每个豆子对于一种划法,直接点,就是有没有被圈住,那就可以往状压方面考虑了
动下手,我们对于每个豆子都向右作一条射线 ( f r o m   @ h z o i l i u c h a n g ) : (from\ @hzoi_liuchang ): (from @hzoiliuchang)
在这里插入图片描述
不难发现,此时判定一个豆子是否被圈住,可以变成判断射线与划出的图形的交点的奇偶性
需要转换下思维,因为我们划范围的时候坐标表示的是图中的格点,但豆子却是占一个网格的,所以在判定的时候需要格外注意
f x , y , s f_{x,y,s} fx,y,s表示当前划范围的最后停在的坐标位于 ( x , y ) (x,y) (x,y),此时被圈住的豆子的状态为 s s s时的步数
我们每次枚举一个起点,向四周不断拓展,当然记忆化是必不可少的
( x , y ) (x,y) (x,y)为起点的答案,就是所有 s s s中的最大值


代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long 
using namespace std;
inline LL read()
{
	LL s=0,f=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
	return s*f;
}
int d[15],v[1000];
int t[15][15];
struct qwq{
	int x,y,s;
};
int n,m,D;
int dis[15][15][1000],tf[15][15][1000];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int ix[15],iy[15],ans=0;
void bfs(int xx,int yy)
{
	queue<qwq> q;
	q.push((qwq){xx,yy,0});
	memset(tf,0,sizeof(tf));
	memset(dis,0x3f,sizeof(dis));
	dis[xx][yy][0]=0; tf[xx][yy][0]=1;
	while(q.size())
	{
		int x=q.front().x,y=q.front().y,s=q.front().s,bc=s;
		q.pop();
		for(int i=0;i<4;i++)
		{
			int vx=x+dx[i],vy=y+dy[i];
			if(vx<1||vy<1||vx>n||vy>m||t[vx][vy]) continue;
			s=bc;
			if(i==0||i==2)
			  for(int k=1;k<=D;k++)
				if((x<ix[k]&&vx==ix[k]||x==ix[k]&&vx<ix[k])&&vy>iy[k]) 
				  s^=(1<<(k-1));
			if(tf[vx][vy][s]) continue;
			if(vx==1&&vy==1)
			  vx++,vx--;
			tf[vx][vy][s]=1;
			dis[vx][vy][s]=dis[x][y][bc]+1;
			q.push((qwq){vx,vy,s});
		}
	}
	for(int i=0;i<(1<<D);i++) ans=max(ans,v[i]-dis[xx][yy][i]);
	return;
}
int main()
{
//	freopen("data.in","r",stdin);
//	freopen("1.out","w",stdout);
	n=read();m=read();D=read();
	for(int i=1;i<=D;i++) d[i]=read();
	for(int i=0;i<(1<<D);i++)
	  for(int k=0;k<D ;k++)
	    v[i]+=d[k+1]*((i&(1<<k))?1:0);
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	  {
	  	char c=getchar();
	  	while((c<'0'||c>'9')&&c!='#') c=getchar();
	  	if(c=='0') t[i][j]=0; else t[i][j]=1;
	  	if(c>'0'&&c<='9') ix[c-'0']=i,iy[c-'0']=j;
	  }
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
		if(!t[i][j]) bfs(i,j);
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值