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. 微信内置浏览器私有接口WeixinJSBridge介绍

    原文地址:http://www.3lian.com/edu/2015/05-25/216227.html 这篇文章主要介绍了微信内置浏览器私有接口WeixinJSBridge介绍,本文讲解了发送给好友 ...

  2. C# 中使用Linq和Lambda表达式对List<T>进行排序

    C#中List<T>排序的两种方法 List<Student> stu = (List<Student>)Session["StudentList&quo ...

  3. java面试和笔试

    1.Java中异常处理机制和事件机制. 2.String是最基本的数据类型吗? 基本数据类型包括byte.int.char.long.float.double.boolean和short. java. ...

  4. 3、JUC--ConcurrentHashMap 锁分段机制

    ConcurrentHashMap  Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能.  ConcurrentHashMap 同步容器 ...

  5. 关于 kali linux

    2.更新系统:首先更换一个速度快点的国内源(1) lsb_release -a先看你的版本,是Rolling还是其他什么(2) leafpad /etc/apt/sources.list(源的默认文件 ...

  6. 【ElasticSearch】:Mapping相关

    Mapping 类似数据库中的表结构定义,主要作用如下: 定义Index下的字段名(Field Name). 定义字段类型,例如数值型.字符串型.布尔型等. 定义倒排索引相关配置,比如是否索引.记录p ...

  7. hadoop集群搭建(hdfs)

    (搭建hadoop集群的前提是服务器已成功安装jdk以及服务器之间已设置免密码登录,服务器之间的免密码登录可参考<linux服务器间ssh免密码登录>) 1.下载hadoop安装包 wge ...

  8. String s=“dd”和String s=new String("dd")区别

    Java中String s="dd"的话会先检查常量池中是否有"dd"这个字符串,如果没有则创建一个,然后将s指向字符串的地址,而new String(&quo ...

  9. 机器学习与Tensorflow(3)—— 机器学习及MNIST数据集分类优化

    一.二次代价函数 1. 形式: 其中,C为代价函数,X表示样本,Y表示实际值,a表示输出值,n为样本总数 2. 利用梯度下降法调整权值参数大小,推导过程如下图所示: 根据结果可得,权重w和偏置b的梯度 ...

  10. Runtime 全方位装逼指南

    Runtime是什么?见名知意,其概念无非就是“因为 Objective-C 是一门动态语言,所以它需要一个运行时系统……这就是 Runtime 系统”云云.对博主这种菜鸟而言,Runtime 在实际 ...