题意
菲菲和牛牛在一块 n n n 行 m m m 列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。
棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。
落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。
棋盘的每个格子上,都写有两个非负整数,从上到下第 i i i 行中从左到右第 j j j 列的格子上的两个整数记作 a i , j a_{i,j} ai,j 和 b i , j b_{i,j} bi,j。
在游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的 a i , j a_{i,j} ai,j 之和,牛牛的得分是所有有白棋的格子上的 b i , j b_{i,j} bi,j 的和。
菲菲和牛牛都希望,自己的得分减去对方的得分得到的结果最大。现在他们想知道,在给定的棋盘上,如果双方都采用最优策略且知道对方会采用最优策略,那么,最终的结果如何?
1 ≤ n , m ≤ 10 1 \leq n, m \leq 10 1≤n,m≤10, 0 ≤ a i , j , b i , j ≤ 1 0 5 0 \leq a_{i, j}, b_{i, j} \leq 10^5 0≤ai,j,bi,j≤105。
更多样例解释,敬请参考洛谷。
思路
这种题好像叫做 min-max 搜索。
根据博弈规则,先手肯定在能下的位置上挑一个最大的 a a a 下,后手肯定在能下的位置上挑一个最小的 b b b 下。即先手想最大化 s u m a − s u m b sum_a-sum_b suma−sumb,后手想最大化 s u m b − s u m a sum_b-sum_a sumb−suma,即最小化 s u m a − s u m b sum_a-sum_b suma−sumb。
看看下棋规则:“一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子”。那么,第一行除外,任一一行 i i i 可以再填棋子,当且仅当 i i i 行棋子个数小于 i − 1 i-1 i−1 行棋子个数,即“锯齿状”。
看到 n , m n,m n,m 比较小,我们可以考虑状压;或者说对于这种问题,典型的,我们可以使用“轮廓线 dp”,因为在这题有明显的轮廓线特征。
引入数组 l k s i lks_i lksi 表示第 i i i 行填了多少个;我们刚刚说要状压,那怎么表示出一个“状态”呢?不妨就将每行的 l k s i lks_i lksi 合起来变成一个 n n n 位状态。
ll get_hash()//轮廓线哈希值
{
ll ret=0;
for(int i=1;i<=n;i++)//低位在 1
ret=ret*11+lks[i];//m<=10
return ret;
}
用一个 map 记搜即可。
那么这题就会变成一道简单暴力搜索题了。
考虑用 ∑ i = 1 n l k s i \displaystyle\sum_{i=1}^n lks_i i=1∑nlksi 的奇偶判断先手后手,每次直接扫 n n n 行。上文说过一行能填当且仅当 l k s i − 1 > l k s i lks_{i-1}>lks_i lksi−1>lksi(此处让 l k s 0 lks_0 lks0 变成极大值),如果要在本行末尾填,就 l k s i + 1 lks_i+1 lksi+1。
上文还说了,“即先手想最大化 s u m a − s u m b sum_a-sum_b suma−sumb,后手想最小化 s u m a − s u m b sum_a-sum_b suma−sumb”,那么根据先后手分情况讨论即可。先手(菲菲)下就求后继状态加 a i , l k s i a_{i,lks_i} ai,lksi 的最大值,反之就求后继状态减 b i , l k s i b_{i,lks_i} bi,lksi 的最小值。
记得还原 l k s i lks_i lksi:
ll tem=0;
for(int i=1;i<=n;i++)
tem+=lks[i];
ll op=tem&1;//0先大1后小
ll ret=(op?inf:-inf);
for(int i=1;i<=n;i++)
{
if(lks[i-1]<=lks[i])continue;//不是锯齿不合法
lks[i]++;//右边填一个
ll t=get_hash();
if(op)ret=min(ret,dfs(t)-b[i][lks[i]]);
else ret=max(ret,dfs(t)+a[i][lks[i]]);
lks[i]--;
}
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=13,inf=0x7f7f7f7f;
ll n,m,a[N][N],b[N][N];
ll lks[N];//轮廓线,右到了哪一列
map<ll,ll>mp;
ll get_hash()//轮廓线哈希值
{
ll ret=0;
for(int i=1;i<=n;i++)//低位在 1
ret=ret*11+lks[i];
return ret;
}
void get_lks(ll s)
{
for(int i=n;i>=1;i--)
lks[i]=s%11,s/=11;
}
ll dfs(ll s)//轮廓线状态
{
if(mp.count(s))return mp[s];
get_lks(s);
ll tem=0;
for(int i=1;i<=n;i++)
tem+=lks[i];
ll op=tem&1;//0先大1后小
ll ret=(op?inf:-inf);
for(int i=1;i<=n;i++)
{
if(lks[i-1]<=lks[i])continue;//不是锯齿不合法
lks[i]++;//右边填一个
ll t=get_hash();
if(op)ret=min(ret,dfs(t)-b[i][lks[i]]);
else ret=max(ret,dfs(t)+a[i][lks[i]]);
lks[i]--;
}
mp[s]=ret;
return ret;
}
ll M;
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&b[i][j]);
lks[0]=m;
for(int i=1;i<=n;i++)
M=M*11+m;//全满状态
mp[M]=0;//一个一个清开不下
printf("%lld",dfs(0));
return 0;
}