2018 省选 T1 一双木棋
枫林晚
2018-04-12 22:22:06
### 题目描述
菲菲和牛牛在一块n 行m 列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。
落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。
棋盘的每个格子上,都写有两个非负整数,从上到下第i 行中从左到右第j 列的格 子上的两个整数记作Ai,jA_{i,j}Ai,j 、Bi,jB_{i,j}Bi,j 。在游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的Ai,jA_{i,j}Ai,j 之和,牛牛的得分是所有有白棋的格子上的Bi,jB_{i,j}Bi,j 的和。
菲菲和牛牛都希望,自己的得分减去对方的得分得到的结果最大。现在他们想知道,在给定的棋盘上,如果双方都采用最优策略且知道对方会采用最优策略,那么,最终的结果如何。
输入输出格式
输入格式:
从文件chess.in 中读入数据。
输入第一行包含两个正整数n;m,保证n;m <= 10。
接下来n 行,每行m 个非负整数,按从上到下从左到右的顺序描述每个格子上的 第一个非负整数:其中第i 行中第j 个数表示Ai,jA_{i,j}Ai,j 。
接下来n 行,每行m 个非负整数,按从上到下从左到右的顺序描述每个格子上的 第二个非负整数:其中第i 行中第j 个数表示Bi,jB_{i,j}Bi,j 。
### 输出格式:
输出到文件chess.out 中。
输出一个整数,表示菲菲的得分减去牛牛的得分的结果。
### 分析:
首先,左边、上边所有格子和左边格子、上边格子都填满其实是一样的。
可以通过n/m <=10 想到状压dp
一般大家用的状压dp都是维护之前的几行从左数有几个旗子已经下过。
因为发现,棋盘上下过的地方总是右上角的一个阶梯形状,剩下的总是一个右下角的部分。所以高级的做法是:维护已下过的部分和没有下过的部分的分界线的状态。(1表示横,0表示竖)
状态查看时,从末位向前看,从棋盘左下角划线。
例如样例中,11100是初始状态,00111是最终的状态。
我们可以dfs预处理出所有的状态,C(20,10)种合法状态。接着,我么可以预处理出每个状态的转弯处(0,1交汇处)通过这个转弯处可以下一个棋子,从而转移到下一个状态。
需要注意的是最后work的方法。(又卡了一天)
不能用递推!因为之前的局部最优策略下的最优解可能不是最终局面下的形式。而由于后面的局面“最优解”是通过这个局部策略转移过来的,导致全部错误。
例如:
3 3
9 4 3
6 7 6
4 5 9
0 0 0
0 0 0
0 0 0
错误输出是 29 正解 32
错误的过程是:
9 4 3
0 0 0
4 0 9
正解:
9 0 3
0 7 0
4 0 9
正解中,下棋时会先填满左边一列,而错误解法则是随机的一块中的当前最优解来更新。左上角三个数,错误解法中这就是局部最优解,但是与正解相差甚远。
所以考虑设f[i]表示i状态下,剩下的格子下法中最优解的答案(是一个a-b的差值)
当该a下时,初值f[i]=-inf f[i]=max(f[i],dfs(to,who^1)+a[x][y]);
当该b下时,初值f[i]=inf;
f[i]=min(f[i],dfs(to,who^1)-b[x][y]);
加上记忆化搜索即可。(在这里剪掉的是一种局面可能由多种局面下出来的情况,避免再往后推)
100行代码,不开O2照样水过。
```cpp
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=12;
const int M=184756+5;
const int inf=0x3f3f3f3f;
int f[M];
int ret[1048576];
struct node{
int wei[2*N][4];
int tur;
int zhi;
int size;
}g[M];
int n,m;
int a[N][N],b[N][N];
int cnt=0;
int vis[M];
void dfs(int x,int sum,int num1,int num0)
{
if(x==(n+m)+1)
{
if(num1==m&&num0==n)
{
g[++cnt].zhi=sum;
}
return;
}
dfs(x+1,sum+(1<<x-1),num1+1,num0);
dfs(x+1,sum,num1,num0+1);
}//0 up 1 right
int dfs2(int hao,int who)
{
if(vis[hao]) return f[hao];
int i=hao;
vis[hao]=1;
if(hao==cnt) return 0;
if(who&1) f[hao]=inf;
else f[hao]=-inf;
for(int i=1;i<=g[hao].tur;i++)
{
int h=g[hao].wei[i][0];
int l=g[hao].wei[i][1];
int x=g[hao].wei[i][2];
int y=g[hao].wei[i][3];
int to=ret[g[hao].zhi+(1<<x-1)-(1<<y-1)];
if(who&1) f[hao]=min(f[hao],dfs2(to,1-who)-b[h][l]);
else f[hao]=max(f[hao],dfs2(to,1-who)+a[h][l]);
}
return f[hao];
}
bool cmp(node a,node b)
{
if(a.size!=b.size) return a.size<b.size;
return a.zhi>b.zhi;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&b[i][j]);
dfs(1,0,0,0);
for(int i=1;i<=cnt;i++)
{
int t=g[i].zhi;
int s=0;
int last;
int num[2];
memset(num,0,sizeof num);
while(s!=(n+m))
{
s++;
if(t&1) g[i].size+=n-num[0];
if(s==1) last=(t&1);
else{
if(last==0&&((t&1)==1))
{
g[i].wei[++g[i].tur][0]=n-num[0]+1;
g[i].wei[g[i].tur][1]=num[1]+1;
g[i].wei[g[i].tur][2]=s-1;
g[i].wei[g[i].tur][3]=s;
}
last=(t&1);
}
num[t&1]++;
t>>=1;
}
}
sort(g+1,g+cnt+1,cmp);
for(int i=1;i<=cnt;i++)
ret[g[i].zhi]=i;
printf("%d",dfs2(1,0));
return 0;
}
```
总结:
1.应用的算法:状压dp与记忆化搜索结合。
2.注意转移时的方式和顺序。保证最优子结构。