[九省联考2018] 一双木棋 chess
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的更多相关文章
- 洛谷 P4363 [九省联考2018]一双木棋chess 解题报告
P4363 [九省联考2018]一双木棋chess 题目描述 菲菲和牛牛在一块\(n\)行\(m\)列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手. 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落 ...
- Luogu4363 [九省联考2018]一双木棋chess 【状压DP】【进制转换】
题目分析: 首先跑个暴力,求一下有多少种状态,发现只有18xxxx种,然后每个状态有10的转移,所以复杂度大约是200w,然后利用进制转换的技巧求一下每个状态的十进制码就行了. 代码: #includ ...
- luogu P4363 [九省联考2018]一双木棋chess
传送门 对抗搜索都不会,我真是菜死了qwq 首先根据题目条件,可以发现从上到下每一行的棋子数是单调不增的,然后n m都比较小,如果把状态搜出来,可以发现合法状态并不多,所以可以用一个11进制数表示状态 ...
- [九省联考2018]一双木棋chess
题解: 水题吧 首先很显然的是状压或者搜索 考虑一下能不能状压吧 这个东西一定是长成三角形的样子的 所以是可以状压的 相邻两位之间有几个0代表他们差几 这样最多会有2n 然后就可以转移了 由于之前对博 ...
- 【题解】Luogu P4363 [九省联考2018]一双木棋chess
原题传送门 这道题珂以轮廓线dp解决 经过推导,我们珂以发现下一行的棋子比上一行的棋子少(或等于),而且每一行中的棋子都是从左向右依次排列(从头开始,中间没有空隙) 所以每下完一步棋,棋盘的一部分是有 ...
- P4363 [九省联考2018]一双木棋chess
思路 容易发现只能在轮廓线的拐点处落子,所以棋盘的状态可以用一个n+m长度的二进制数表示 转移就是10变成01 代码 #include <cstdio> #include <algo ...
- BZOJ.5248.[九省联考2018]一双木棋chess(对抗搜索 记忆化)
BZOJ 洛谷P4363 [Update] 19.2.9 重做了遍,感觉之前写的有点扯= = 首先棋子的放置情况是阶梯状的. 其次,无论已经放棋子的格子上哪些是黑棋子哪些是白棋子,之前得分如何,两人在 ...
- [九省联考 2018]一双木棋chess
Description 题库链接 给出一个 \(n\times m\) 的棋盘,棋盘的每个格子有两个权值 \(A,B\) . Alice 和 Bob 轮流操作在棋盘上放棋子,一个格子能放棋子的前提条件 ...
- Luogu 4363 [九省联考2018]一双木棋chess
发现数据范围很小,想到状压dp,然后就愣住不会了. 表示太菜了并没有接触过轮廓线dp这种操作. 首先发现合法的操作过程中一定是这样子的: 按照行来看发现每一行单调不递增. 我们用$1$来表示竖着的轮廓 ...
随机推荐
- HttpWebRequest 跳转后(301,302)ResponseUri乱码问题
问题: 目标地址: http://www.baidu.com/baidu.php?url=a000000aa.7D_ifdr1XkSUzuBz3rd2ccvp2mFoJ3rOUsnx8OdxeOeOL ...
- 【转】 js数组 Array 交集 并集 差集 去重
原文:http://blog.csdn.net/ma_jiang/article/details/52672762 最劲项目需要用到js数组去重和交集的一些运算,我的数组元素个数可能到达1000以上, ...
- canvas制作完美适配分享海报
基于mpvue实现的1080*1900小程序海报 html <canvas class="canvas" :style="'width:'+windowWidt ...
- Shell - 简明Shell入门10 - 管道(Pipe)
示例脚本及注释 #!/bin/bash echo '##### Number of *.conf : ' find /etc -name *.conf | grep system | wc -l ec ...
- D13——C语言基础学PYTHON
C语言基础学习PYTHON——基础学习D13 20180918内容纲要: 堡垒机运维开发 1.堡垒机的介绍 2.堡垒机的架构 3.小结 4.堡垒机的功能实现需求 1 堡垒机的介绍 百度百科 随着信息安 ...
- Python小白学习之路(二十六)—【if __name__ =='__main__':】【用状态标识操作】
规则一: 一个python文件中,只写一些可以运行的功能测试代码写在这句代码下面 if __name__ =='__main__': 在讲这边的时候,我不是很懂参考了一篇博客,地址如下:http:// ...
- 利用Makefile安装helloworld模块(速成)
这学期对了一门操作系统,满怀着好奇装了虚拟机然后安了Ubuntu,这周作业是编译内核和安装个模块,妈耶,折腾了我一两天.终于弄完,CSDN上有挺多类似的教程,例如陈皓的跟我一起写Makefile,写的 ...
- 【learning】 扩展欧几里得算法(扩展gcd)和乘法逆元
有这样的问题: 给你两个整数数$(a,b)$,问你整数$x$和$y$分别取多少时,有$ax+by=gcd(x,y)$,其中$gcd(x,y)$表示$x$和$y$的最大公约数. 数据范围$a,b≤10^ ...
- 解决修改css或js文件,浏览器缓存更新问题。
在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css.js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从 ...
- 【tomcat】servlet原理及其生命周期
1.什么是servlet? Servlet(Servlet Applet),全称Java Servlet,是用Java编写的服务器端程序.而这些Servlet都要实现Servlet这个接口.其主要功能 ...