[SCOI2009]围豆豆题解

状压DP-围豆豆

[SCOI2009]围豆豆

题目背景

四川NOI2009省选

题目描述

是不是平时在手机里玩吃豆豆游戏玩腻了呢?最近MOKIA手机上推出了一种新的围豆豆游戏,大家一起来试一试吧。

游戏的规则非常简单,在一个N×M的矩阵方格内分布着D颗豆子,每颗豆有不同的分值Vi。游戏者可以选择任意一个方格作为起始格,每次移动可以随意的走到相邻的四个格子,直到最终又回到起始格。最终游戏者的得分为所有被路径围住的豆豆的分值总和减去游戏者移动的步数。矩阵中某些格子内设有障碍物,任何时刻游戏者不能进入包含障碍物或豆子的格子。游戏者可能的最低得分为0,即什么都不做。

注意路径包围的概念,即某一颗豆在路径所形成的多边形(可能是含自交的复杂多边形)的内部。下面有两个例子:

第一个例子中,豆在路径围成的矩形内部,所以豆被围住了。第二个例子中,虽然路径经过了豆的周围的8个格子,但是路径形成的多边形内部并不包含豆,所以没有围住豆子。

布布最近迷上了这款游戏,但是怎么玩都拿不了高分。聪明的你决定写一个程序来帮助他顺利通关。

输入格式

第一行两个整数N和M,为矩阵的边长。

第二行一个整数D,为豆子的总个数。

第三行包含D个整数V1到VD,分别为每颗豆子的分值。

接着N行有一个N×M的字符矩阵来描述游戏矩阵状态,0表示空格,#表示障碍物。而数字1到9分别表示对应编号的豆子。

输出格式

仅包含一个整数,为最高可能获得的分值。

样例 #1

样例输入 #1

3 8
3
30 -100 30
00000000
010203#0
00000000

样例输出 #1

38

提示

50%的数据满足1≤D≤3。

100%的数据满足1≤D≤9,1≤N, M≤10,-10000≤Vi≤10000。

P2566 [SCOI2009]围豆豆 题解

嗯,这是一道状压

根据动态规划的步骤,我们首先需要设计出状态,直觉告诉我们应该有一个维度存放状态 S S S, S S S的二进制下每一位代表了是否圈上了那个豆子

而继续观察,需要刻画出这个状态,仅仅有这样一个并不能够唯一确定,故需要唯一确定这个状态,就很好办了,加两个维度表示坐标,当然,这样刻画状态虽然一个状态还是会表示很多种可能,但此时这些可能性都可以被化成一个最优解进而不影响答案,这里我们也可以看出状态压缩动态规划的状态设计就在于设计出来的状态一定是多个可能性的集合,取最优解,但不会影响答案的。

那么我们就可以设 f [ x , y , S ] f[x,y,S] f[x,y,S]表示终点在 ( x , y ) (x,y) (x,y)状态为 S S S的最大得分,这里为了方便计算,在代码中我改了一下,改成了 f [ x , y , S ] f[x,y,S] f[x,y,S]表示终点在 ( x , y ) (x,y) (x,y)状态为 S S S的最小步数,因为知道了 S S S就可以直接算出总的豆豆提供的贡献,那这道题就变成了一个图上的状压?!

那么模型已经出来了,就是借助最短路算法进行状压DP,我们就需要思考如何转移了,比如一个状态 S S S在什么样的情况下可以转到状态 S ′ S' S

很明显,状态发生变化肯定是和路线有关系的,现在需要思考在怎样的一种情况下,一个豆豆会被围住

因为我们的路径最终是一条回路,所以其实上我们只需要考虑包围这个豆豆的多边形的一半即可,可以是豆豆左边的一半,也可以是右边的一半,甚至是上下的一半都行,然后就是在原来做一个数学题的时候遇见过,判定一个点是否位于一个多边形中,只需要判定这个点引出一条射线与整个多边形的交点数量的奇偶性即可。这个玩意儿就可以想到如何转移了,在代码中为了方便我选择了向右引出一条射线

我们可以枚举每一个豆豆,看这一个新的步伐会不会对这个豆豆的包围产生影响,当然,我们向右引出的射线,交点数量只会在这个步伐是上下移动的时候才会改变,具体呈现出这个样子:

两个箭头表示这一步到达的点,两个竖直线段表示这一步,需要注意的是,两个竖直线段在不考虑指向的情况下,必须完全重合,否则会出错

而对于奇偶性,用异或运算进行更改即可,于是更改状态的代码可以写成这个鸭子

int find(int x1,int y1,int x2,int y2,int lst){//(x1,y1)->(x2,y2)
	int now=lst;
	if(x1==x2)return now;
	for(int i=1;i<=cnt;i++){
		if((x1==ax[i]&&x2>ax[i]||x2==ax[i]&&x1>ax[i])&&ay[i]<y2)now=now^(1<<(i-1));
	}
	return now;
}

那么有了状态这些,转移就很简单了,直接略去,代码就张这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,m,ans=0xcfcfcfcf,cnt,dx[4]={0,0,1,-1},dy[4]={1,-1,0,0},ax[11],ay[11],val[11],a[11][11],f[11][11][1<<10],vis[11][11][1<<10],point[1<<10];
int find(int x1,int y1,int x2,int y2,int lst){
	int now=lst;
	if(x1==x2)return now;
	for(int i=1;i<=cnt;i++){
		if((x1==ax[i]&&x2>ax[i]||x2==ax[i]&&x1>ax[i])&&ay[i]<y2)now=now^(1<<(i-1));
	}
	return now;
}
struct node{
	int x,y,now,step;
};
queue<node>q;
void update(node a,node b){
	if(f[a.x][a.y][a.now]+1<f[b.x][b.y][b.now]){
		f[b.x][b.y][b.now]=f[a.x][a.y][a.now]+1;
		q.push(b);
	}
}
int SPFA(node x){
	q.push(x);
	memset(vis,0,sizeof vis);
	memset(f,0x3f,sizeof f);
	f[x.x][x.y][0]=0;
	while(q.size()){
		node u=q.front();q.pop();
		vis[u.x][u.y][u.now]=1;
		for(int i=0;i<4;i++){
			node v={u.x+dx[i],u.y+dy[i],find(u.x,u.y,u.x+dx[i],u.y+dy[i],u.now),u.step+1};
			if(v.x<1||v.x>n||v.y<1||v.y>m||vis[v.x][v.y][v.now]||a[v.x][v.y])continue;
			update(u,v);
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&cnt);
	for(int i=1;i<=cnt;i++)scanf("%d",&val[i]);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char x;
			cin>>x;
			if(x=='#')a[i][j]=1;
			if('1'<=x&&x<='9')a[i][j]=1,ax[x-'0']=i,ay[x-'0']=j;
		}
	}
	for(int i=1;i<1<<cnt;i++){
		for(int j=1;j<=cnt;j++){
			if((i>>(j-1))&1)point[i]+=val[j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j])continue;
			SPFA({i,j,0,0});
			for(int i1=0;i1<(1<<cnt);i1++){
				ans=max(ans,point[i1]-f[i][j][i1]);
			}
		}
	}
	printf("%d\n",ans);
}
```cpp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值