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. postgresql 主从 patroni

    1 安装基础包 1.1 postgres yum install https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_ ...

  2. WPF Command CanExecute 触发一次的问题

    昨天在项目中遇到一个问题,按钮bind了Command后,利用CanExecute控制它的是否可点击.结果却在初始化viewmodel的时候执行了一次CanExecute,之后一直不触发,按钮的可用性 ...

  3. .Net下EF的简单实现

    1.连接SQLServer,创建数据库TestDB; 2.添加EF引用,点击工具-NuGet包管理器-管理解决方案的NuGet程序包, 搜索EntityFramework包,点击安装: 3.在Web. ...

  4. win10 17025 触摸bug

    This article is written in both English and Chinese. 本文使用中文和英文两个版本. 在 win10 的 17025 可以容易让 UWP 触摸失效.做 ...

  5. 背水一战 Windows 10 (54) - 控件(集合类): ItemsControl 的布局控件 - OrientedVirtualizingPanel, VirtualizingStackPanel, WrapGrid

    [源码下载] 背水一战 Windows 10 (54) - 控件(集合类): ItemsControl 的布局控件 - OrientedVirtualizingPanel, VirtualizingS ...

  6. 【js】关于正则表达式

    正则表达式描述了字符的模式对象 语法: var patt=new RegExp(pattern,modifiers); 或更简单的方法 var patt=/pattern/modifiers; 模式描 ...

  7. 优化openfire服务器提升xmpp 效率的15个方法(原创)

    1.禁用原生xmpp搜索,使组织架构.人员数据本地化保存,并使客户端数据同步服务器,降低原生xmpp搜索的iq消耗,因为搜索是im应用的频繁操作: 2.禁用roster花名册.禁用presence包通 ...

  8. 配置django图片上传与保存展示

    近来在研究django,发现有好多好玩的功能,比如图片上传,以前处理这个比较麻烦,现在我们来看看如何来处理图片上传与保存 1.在数据库设计的时候需要配置upload_to image = models ...

  9. Eclipse连接MuMu模拟器 方便 测试 查日志

    Eclipse连接MuMu模拟器 方便 测试 查日志 问题由来 真机测试麻烦(首先你得拿一部手机,然后在用数据线连接电脑和手机...) 解决流程 确保打开MuMu模拟器和Eclipse的DDMS功能 ...

  10. odoo开发笔记:Server+Action服务器动作自动触发执行

           Odoo的市场定位是SME(中小型企业),这个市场的ERP产品,多如牛毛,产品各具特色.不过,Odoo的自动化处理机制,可以睥睨天下,无人能及.包括一些大型国产软件,如用友.金蝶也不具备 ...