【杭电2015年12月校赛G】【map记录 状压DP 记忆化搜索实现 】Pick Game nm棋盘两人轮流取数 所取位置周围至少2个为空 为先手最大取得权值


Pick Game

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 151    Accepted Submission(s): 31


Problem Description
This is a pick game.
On a n*m matrix, each gird has a value. The player could only choose the gird that is adjacent to at least two empty girds (A grid outside the matrix also regard as empty). Adjacent means two girds share a common edge. If one play chooses one gird, he will get the value and the gird will be empty. They play in turn.

One day, WKC plays this game with ZJS.  
Both of them are clever students, so they will choose the best strategy.  
WKC plays first, and he wants to know the maximal value he could get.
 

Input
There is a positive integer T(1<=T<=50) in the first line, which specifying the number of test cases to follow.
Each test case begins with two numbers n and m ( 2 <= n, m <= 5 ).
Then n lines follow and each lines with m numbers V ij  (0< V ij  <=1000).
 

Output
Output the maximal value WKC could get.
 

Sample Input
  
  
1 2 2 9 8 7 6
 

Sample Output
  
  
16
 



#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=0,M=0,Z=1e9+7,ms63=0x3f3f3f3f;
int casenum,casei;
int n,m,top;
int a[5][5];
int res[5][5];
int dy[4]={-1,0,0,1};
int dx[4]={0,-1,1,0};
int b[32];
inline bool ok(int y,int x)
{
	return y>=0&&y<n&&x>=0&&x<m;
}
map<int,int>mop;
inline int dfs(int sta,int sum)
{
	if(mop.find(sta)!=mop.end())return mop[sta];
	int maxv=0;
	for(int i=0;i<n;++i)
	{
		for(int j=0;j<m;++j)if(res[i][j]<=2)
		{
			int o=i*m+j;
			if(sta&b[o])
			{
				for(int k=0;k<4;++k)
				{
					int yy=i+dy[k];
					int xx=j+dx[k];
					if(ok(yy,xx))--res[yy][xx];
				}
				int tmp=sum-dfs(sta^b[o],sum-a[i][j]);
				gmax(maxv,tmp);
				for(int k=0;k<4;++k)
				{
					int yy=i+dy[k];
					int xx=j+dx[k];
					if(ok(yy,xx))++res[yy][xx];
				}
			}
		}
	}
	mop[sta]=maxv;
	return maxv;
}
int main()
{
	for(int i=0;i<=30;++i)b[i]=1<<i;
	scanf("%d",&casenum);
	for(casei=1;casei<=casenum;++casei)
	{
		mop.clear();
		int sum=0;
		MS(res,0);
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;++i)
		{
			for(int j=0;j<m;++j)
			{
				scanf("%d",&a[i][j]);
				sum+=a[i][j];
				for(int k=0;k<4;++k)
				{
					int yy=i+dy[k];
					int xx=j+dx[k];
					if(ok(yy,xx))++res[i][j];
				}
			}
			
		}
		mop[0]=0;
		printf("%d\n",dfs(b[n*m]-1,sum));
	}
	return 0;
}
/*
【trick&&吐槽】
这题原来这么水= =
明明可以AC的,看到过的人少就不敢做了,是病得治啊>_<
我竟然放弃了这样题面的一道题,去做计算几何!简直——作!大!死!

(话说这题面为什么是这个 我我我我也不知道!)

ps:没有做的另外一个原因是,我竟然又读错题了= 。 =

【题意】
给你一个n*m(2<=n,m<=5)的棋盘。
每个方格都有一个棋子,权值为v[][](0<=v[][]<=1000)两个人轮流取数。

一个棋子可以被选的条件是,这个棋子上下左右4个方向中,至少有2个棋子为空或者被取过。
两个人都希望自己的权值尽可能大,都采取最优策略。
让你输出,首先最大可以获得的权值是多少。

【类型】
记忆化搜索

【分析】
这题很类似于旅行商问题。

我们发现——
棋子数量做多也不过只有25,状态数最多只有2^25,状态数确定,对应权值就确定,和顺序无关,可以很轻松表示。
同时,因为我们取数有一定的限制条件,所以合法的状态数更少。
虽然状态数的总数没那么多,然而状态数的表示,最大还是需要用到2^25,用int存不下,于是考虑用map。

=============================================================================
工具都准备好了,开始做这道题啦。

不妨定义状态——
mop[sta]表示现在还没有拿的棋子编号的二进制之和记做sta下,先手取数能拿到的最大权值。
同时用辅助变量sum,记录当前所有未取之数权值之和。

显然,
对于初始条件,我们有——
mop[0]=0;
对于转移,我们有——
mop[sta]=max(sum-mop[nxtsta])

于是,取数限制用res[][]表示,同时套一个记忆化搜索的外壳,这道题就做完了>_<。
然而,现实中的WKC真是太蠢了 T_____T!

【时间复杂度&&优化】
O(合法状态数*log(合法状态数)*25)

这道题一个小优化,就是,我们把当前能选取的位置,
通过一个数组存起来。这样就可以把*25的这个常数优化得小一点了~

*/

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值