Description

菲菲和牛牛在一块n 行m 列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。

落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。

棋盘的每个格子上,都写有两个非负整数,从上到下第i 行中从左到右第j 列的格 子上的两个整数记作Ai,j,,Bi,j 。在游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的Ai,j​ 之和,牛牛的得分是所有有白棋的格子上的Bi,j 的和。

菲菲和牛牛都希望,自己的得分减去对方的得分得到的结果最大。现在他们想知道,在给定的棋盘上,如果双方都采用最优策略且知道对方会采用最优策略,那么,最终的结果如何。

Input

输入第一行包含两个正整数n;m,保证n;m <= 10。

接下来n 行,每行m 个非负整数,按从上到下从左到右的顺序描述每个格子上的 第一个非负整数:其中第i 行中第j 个数表示Ai,j

接下来n 行,每行m 个非负整数,按从上到下从左到右的顺序描述每个格子上的 第二个非负整数:其中第i 行中第j 个数表示Bi,j​ 。

Output

输出一个整数,表示菲菲的得分减去牛牛的得分的结果。

对于所有的测试数据,n;m <= 10 ,Ai,j; Bi,j​ <= 100000。

对于编号为奇数的测试点,保证所有的Bi,j​ = 0 。

Solution

考场上没想出来写的 30 分暴力诶

没想到现在就已经会了

我们定义某一时刻棋盘上的落子情况为当前的状态

定义 s 为初状态,即棋盘上还没有落子

定义 t 为末状态,即棋盘上已经落完子

不难证明,合法的状态小于二十万种

那么先 HASH 一下每个状态,令其唯一对应一个正整数

对于每一个状态,我们可以知道它是从哪些状态转移来的

定义 num[i] 表示 i 状态落了多少子,方便判断当前是该先手还是该后手。

我们 dp 要倒着推,因为如果正着推,有可能出现当前虽然求出了最大价值,但是却不是他们的最优策略的情况。

所以定义 f[i] 表示从状态 i 到末状态 t 先手减后手的最大价值

f[t] 初值为0,f[1] 即为答案

但是怎么求中间状态 f[i] 的值呢?

之前提到过,可以求出 i 状态是由 哪些状态转移来的,假设有一个状态为 j 可以转移到 i

我们用 num 数组求出在状态 j 时是先手下了还是后手下了最后一个棋子,然后分情况讨论

如果是先手:考虑后手的最优策略,显然是想让 f[i] 最小,所以 f[i]=min{f[j]-b[x][y]},x、y 是 j 转移到 i 状态落子的横纵坐标

同理,如果为后手:那么 f[i] 最大的转移方程是 f[i]=max{f[j]+a[x][y]},x、y 的意义跟上面一样

那我们现在就剩最后一个问题了:怎么进行转移呢?

我这里利用了拓扑序进行转移:如果一个状态被所有的后续状态遍历完并求出最优解后,就将其 push 进队列里,让它去转移别人即可。

最坏情况时间复杂度 $O(18万*180万)$ 但是开氧气优化跑的贼快,最慢的点 300ms (反正省选也开 O2 不算作弊)

Code

// By YoungNeal
#include<map>
#include<queue>
#include<cstdio>
#include<cctype>
#define N 400005
#define int long long
#define mod 1000000007
using namespace std; int head[N];
int cnt,s,t;
int n,m,tot;
int qp[N][];
int f[N],fz[];
int deg[N],num[N];
int a[][],b[][]; map<int,int> mp;
queue<int> topo; struct Edge{
int to,nxt,disa,disb;
}edge[N*]; void add(int x,int y,int z,int p){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
edge[cnt].disa=z;
edge[cnt].disb=p;
head[x]=cnt;
} void hsh(int x){
int d=;tot++;
for(int i=;i<=n;i++)
d=d*+fz[i],d%=mod,qp[tot][i]=fz[i];
mp[d]=tot;
num[tot]=x;
if(num[tot]&) f[tot]=2e18;
else f[tot]=-2e18;
} void dfs(int now,int lim,int num){
if(now>n){
hsh(num);
return;
}
for(int i=;i<=lim;i++)
fz[now]=i,dfs(now+,i,num+i);
} void _find(){
int x=,y=;
for(int i=;i<=n;i++) y=y*+m,y%=mod;
s=mp[x],t=mp[y];
} void read(int &x){
x=;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<)+(x<<)+(ch^),ch=getchar();
} signed main(){
scanf("%lld%lld",&n,&m);
for(int i=;i<=n;i++){
for(int j=;j<=m;j++) read(a[i][j]);
}
for(int i=;i<=n;i++){
for(int j=;j<=m;j++) read(b[i][j]);
}
dfs(,m,);
_find();
f[t]=;
for(int i=;i<=tot;i++){
for(int j=n;j;j--){
if(qp[i][j]==qp[i][j+]) continue;
int x=;int idx=j,idy=qp[i][j];
for(int p=;p<=n;p++){
if(p==j) x=x*+qp[i][j]-,x%=mod;
else x=x*+qp[i][p],x%=mod;
}
if(num[i]&) add(i,mp[x],a[idx][idy],);
else add(i,mp[x],,b[idx][idy]);
deg[mp[x]]++;
}
}
topo.push(t);
while(topo.size()){
int u=topo.front();topo.pop();
for(int i=head[u];i;i=edge[i].nxt){
int to=edge[i].to;
if(num[to]&) f[to]=min(f[to],f[u]-edge[i].disb);
else f[to]=max(f[to],f[u]+edge[i].disa);
deg[to]--;
if(!deg[to]) topo.push(to);
}
}
printf("%lld\n",f[]);
return ;
}

[九省联考2018] 一双木棋 chess的更多相关文章

  1. 洛谷 P4363 [九省联考2018]一双木棋chess 解题报告

    P4363 [九省联考2018]一双木棋chess 题目描述 菲菲和牛牛在一块\(n\)行\(m\)列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手. 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落 ...

  2. Luogu4363 [九省联考2018]一双木棋chess 【状压DP】【进制转换】

    题目分析: 首先跑个暴力,求一下有多少种状态,发现只有18xxxx种,然后每个状态有10的转移,所以复杂度大约是200w,然后利用进制转换的技巧求一下每个状态的十进制码就行了. 代码: #includ ...

  3. luogu P4363 [九省联考2018]一双木棋chess

    传送门 对抗搜索都不会,我真是菜死了qwq 首先根据题目条件,可以发现从上到下每一行的棋子数是单调不增的,然后n m都比较小,如果把状态搜出来,可以发现合法状态并不多,所以可以用一个11进制数表示状态 ...

  4. [九省联考2018]一双木棋chess

    题解: 水题吧 首先很显然的是状压或者搜索 考虑一下能不能状压吧 这个东西一定是长成三角形的样子的 所以是可以状压的 相邻两位之间有几个0代表他们差几 这样最多会有2n 然后就可以转移了 由于之前对博 ...

  5. 【题解】Luogu P4363 [九省联考2018]一双木棋chess

    原题传送门 这道题珂以轮廓线dp解决 经过推导,我们珂以发现下一行的棋子比上一行的棋子少(或等于),而且每一行中的棋子都是从左向右依次排列(从头开始,中间没有空隙) 所以每下完一步棋,棋盘的一部分是有 ...

  6. P4363 [九省联考2018]一双木棋chess

    思路 容易发现只能在轮廓线的拐点处落子,所以棋盘的状态可以用一个n+m长度的二进制数表示 转移就是10变成01 代码 #include <cstdio> #include <algo ...

  7. BZOJ.5248.[九省联考2018]一双木棋chess(对抗搜索 记忆化)

    BZOJ 洛谷P4363 [Update] 19.2.9 重做了遍,感觉之前写的有点扯= = 首先棋子的放置情况是阶梯状的. 其次,无论已经放棋子的格子上哪些是黑棋子哪些是白棋子,之前得分如何,两人在 ...

  8. [九省联考 2018]一双木棋chess

    Description 题库链接 给出一个 \(n\times m\) 的棋盘,棋盘的每个格子有两个权值 \(A,B\) . Alice 和 Bob 轮流操作在棋盘上放棋子,一个格子能放棋子的前提条件 ...

  9. Luogu 4363 [九省联考2018]一双木棋chess

    发现数据范围很小,想到状压dp,然后就愣住不会了. 表示太菜了并没有接触过轮廓线dp这种操作. 首先发现合法的操作过程中一定是这样子的: 按照行来看发现每一行单调不递增. 我们用$1$来表示竖着的轮廓 ...

随机推荐

  1. centos下完全卸载mysql(别人写的,我仅仅为了学习记录)

    yum方式安装的mysql 1.yum remove mysql mysql-server mysql-libs compat-mysql51 2.rm -rf /var/lib/mysql 3.rm ...

  2. java解决共享资源竞争

    由于多线程的实现,在运行一个程序的时候可能会有很多的线程在同时运行,但是线程的调度并不是可见的,所以不会知道一个线程什么时候在运行,比如说 你坐在桌子前手拿着叉子,正要去叉盘中的最后一片食物,当你的叉 ...

  3. [WPF]为旧版本的应用添加触控支持

    之前做WPF开发时曾经遇到这样一个需求:为一个基于 .NET Framework 3.5开发的老旧WPF程序添加触控支持,以便于大屏触控展示. 接手之后发现这是一个大坑. 项目最初的时候完全没考虑过软 ...

  4. .NET MVC 学习笔记(六)— 数据导入

    .NET MVC 学习笔记(六)—— 数据导入 在程序使用过程中,有时候需要新增大量数据,这样一条条数据去Add明显不是很友好,这时候最好就是有一个导入功能,导入所需要的数据,下面我们就一起来看一下导 ...

  5. [Sdoi2013]费用流(最大流,二分答案)

    前言 网络流的练习为什么我又排在最后啊!!! Solution 我们先来挖掘一个式子: \[ ab+cd>ad+bc(a<c,b<d) \] 这个的证明很显然对吧. 然后就考虑最优策 ...

  6. 04_python_列表

    一.列表 列表是用[ ]括起来并每个元素用逗号分割的,并且可以存放各种数据类型,存放的数据量非常大,列表是有序的(按照你保存的顺序),有索引, 可以切片方便取值. lst = [1, '哈哈', &q ...

  7. sublime text 文件打开时回调一些函数

    需求:公司服务端脚本以 .s 结尾的文件,也按 js 语法识别,方便查看函数定义. 每次都 ss:js 比较麻烦,所以写个插件. import sublime, sublime_plugin clas ...

  8. PowerDesigner生成OOM时类名属性名转换

    Examples Script 1: Convert a name into a class code (JAVA naming convention)转换类名 .foreach_part(%Name ...

  9. SQLAlchemy 快速入门、基础知识

    SQLAlchemy 是Python 编程语言下的一款开源软件.提供了SQL工具包及对象关系映射(ORM)工具. ORM, 全称Object Relational Mapping, 中文叫做对象关系映 ...

  10. mongodb的Snapshot 隔离级别(记住)

    Snapshot 隔离和 Row Version的工作模式 当启用Snapshot隔离级别时,每一个更新数据的操作都会在tempdb中存储该行的原始副本,术语叫作行版本(RowVersion),SQL ...