题面

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

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

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

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

题解

简单题。考场上的我是傻逼。

状压DP是容易想到的思路。若\(s\)是一个状态(先不管它是怎么压出来的),若\(s\)状态中有偶数个格子有棋子(现在轮到A下了),\(f[s]\)表示该状态之后的A得分与B得分之差的最大值(因为A想让这个得分差尽可能大);反之,若\(s\)中有奇数个格子有棋子,则轮到B下了,\(f[s]\)表示该状态之后的A得分与B得分之差的最小值

注意\(f[s]\)表示的是达到状态\(s\)之后的落子造成的得分差,而不是之前的!因为两个人的决策显然是根据【未来】做出的,而不是过去……(所以应该倒着枚举\(s\))

首先,一个格子能落棋子当且仅当:【棋盘左上角到该格子】这个矩形中,只有该格子是空的。

那么每时每刻,有棋子的格子和空格子的分布一定是形如这样的('#'代表有棋子,'-'代表无棋子)

  1. ######
  2. ###---
  3. ##----
  4. ##----
  5. #-----

这启发了我们如何进行状压。

想象'#'与'-'的分界线,像这样:

  1. ___|
  2. _|
  3. |
  4. _|
  5. _|

显然,分界线永远从左下走向右上,期间只【向左走】或【向上走】。

用0和1表示分界线的形状:1表示向上走一格,0表示向右走一格。

例如这个局面

  1. ####
  2. ###
  3. ###
  4. #

它的分界线形状可以表示为01001101,是一个长为\(n + m\)的01串。

那么把01串作为二进制数来表示状态。由于串中一定有且只有\(n\)个1,所以总共有效的状态只有\(C_{n + m}^{n}\)个,但是直接\(2^{n + m}\)枚举就可以通过了。

总结一下:由大到小枚举状态\(s\),每次把其中的一个10改为01(这样分界线向外弯了一格,设这格为\((x, y)\)),设新状态为\(t\),则若状态\(s\)中'#'有偶数个,\(f[s] = max\{f[t] + A_{x, y}\}\),否则\(f[s] = min\{f[t] - B_{x, y}\}\)。

代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <cmath>
  4. #include <algorithm>
  5. #include <iostream>
  6. #define enter putchar('\n')
  7. #define space putchar(' ')
  8. using namespace std;
  9. typedef long long ll;
  10. template <class T>
  11. void read(T &x){
  12. char c;
  13. bool op = 0;
  14. while(c = getchar(), c < '0' || c > '9')
  15. if(c == '-') op = 1;
  16. x = c - '0';
  17. while(c = getchar(), c >= '0' && c <= '9')
  18. x = x * 10 + c - '0';
  19. if(op == 1) x = -x;
  20. }
  21. template <class T>
  22. void write(T x){
  23. if(x < 0) putchar('-'), x = -x;
  24. if(x >= 10) write(x / 10);
  25. putchar('0' + x % 10);
  26. }
  27. const int N = 15, INF = 0x3f3f3f3f;
  28. int n, m, a[N][N], b[N][N], f[1 << 20];
  29. void debug(int x){
  30. for(int i = 0; i < (n + m); i++)
  31. putchar('0' + (x >> i & 1));
  32. }
  33. int main(){
  34. read(n), read(m);
  35. for(int i = 1; i <= n; i++)
  36. for(int j = 1; j <= m; j++)
  37. read(a[i][j]);
  38. for(int i = 1; i <= n; i++)
  39. for(int j = 1; j <= m; j++)
  40. read(b[i][j]);
  41. for(int s = (1 << (n + m)) - 1, fi = 1; s >= 0; s--){
  42. int cnt1 = 0, area = 0;
  43. for(int i = 0; i < n + m; i++)
  44. if(s >> i & 1) cnt1++;
  45. if(cnt1 != n) continue;
  46. if(fi){
  47. fi = 0;
  48. continue;
  49. }
  50. for(int i = 0, x = n + 1, y = 1; i < n + m; i++){
  51. if(s >> i & 1) x--;
  52. else if(y++ <= m) area += x - 1;
  53. }
  54. f[s] = (area & 1) ? INF : -INF;
  55. for(int i = 0, x = n + 1, y = 1; i < n + m; i++){
  56. if(s >> i & 1) x--;
  57. else{
  58. if(i && (s >> (i - 1) & 1)){
  59. if(area & 1) f[s] = min(f[s], f[s ^ (3 << (i - 1))] - b[x][y]);
  60. else f[s] = max(f[s], f[s ^ (3 << (i - 1))] + a[x][y]);
  61. }
  62. y++;
  63. }
  64. }
  65. }
  66. write(f[(1 << n) - 1]), enter;
  67. return 0;
  68. }

[BZOJ5248] 2018九省联考 D1T1 一双木棋 | 博弈论 状压DP的更多相关文章

  1. [BZOJ5248][2018九省联考]一双木棋

    题目描述 https://www.lydsy.com/JudgeOnline/problem.php?id=5248   Solution 我们首先考虑放棋子的操作 发现它一定放棋子的部分是一个联通块 ...

  2. bzoj5248 [2018多省省队联测]一双木棋

    直接hash+爆搜即可. #include <cstdio> #include <cstring> #include <iostream> #include < ...

  3. bzoj千题计划307:bzoj5248: [2018多省省队联测]一双木棋

    https://www.lydsy.com/JudgeOnline/problem.php?id=5248 先手希望先手得分减后手得分最大,后手希望先手得分减后手得分最小 棋盘的局面一定是阶梯状,且从 ...

  4. bzoj5248(洛谷4363)(2018九省联考)一双木棋

    题目:https://www.luogu.org/problemnew/show/P4363 一种考虑状态数的方法:有几个用了k个格子的列,就在第k个0的左边插入几个1: 这也是求不降序列的个数的方法 ...

  5. BZOJ5248:[九省联考2018]一双木棋——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5248 https://www.luogu.org/problemnew/show/P4363#su ...

  6. 九省联考2018 D1T1 一双木棋

    Alice和Bob轮流在n*m的棋盘上放棋子 a[i][j]表示Alice放在这的收益,b[i][j]表示Bob放在这的收益 一个地方没有棋子且它的左边上边都有棋子才能放棋子,边界外视为有一圈棋子 n ...

  7. 2018九省联考(SHOI2018)

    听说在退役前还能有去外省的机会QAQ D1 9点T1,T2过拍,感觉自己稳得一批,然后边看T3边幻想AK 事实证明我是多么菜多么无知多么傻逼 想T3时太浮躁,最后也没想出来 T2根本没有想过去怀疑自己 ...

  8. bzoj 5248: [2018多省省队联测]一双木棋

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

  9. 2018.11.06 bzoj1097: [POI2007]旅游景点atr(最短路+状压dp)

    传送门 预处理出不能在每个点停留之后才停留的点的状态. 对kkk个点都跑一次最短路存下来之后只需要简单状压一下就能过了吐槽原题空间64MB蒟蒻无能为力 然后用fillfillfill赋极大值的时候当m ...

随机推荐

  1. 【转】单KEY业务,数据库水平切分架构实践

    本文将以“用户中心”为例,介绍“单KEY”类业务,随着数据量的逐步增大,数据库性能显著降低,数据库水平切分相关的架构实践: 如何来实施水平切分 水平切分后常见的问题 典型问题的优化思路及实践 一.用户 ...

  2. Combobox值自定义(不通过数据库)

    前台 <ext:ComboBox ID="cmbYear" runat="server"/> .aspx.cs int year = DateTim ...

  3. dotnetcore/CAP

    CAP带你轻松玩转Asp.Net Core消息队列 CAP是什么? CAP是由我们园子里的杨晓东大神开发出来的一套分布式事务的决绝方案,是.Net Core Community中的第一个千星项目(目前 ...

  4. pycharm2019注册码一键实时获取,永久有效!

    pycharm2019专业版激活码 56ZS5PQ1RF-eyJsaWNlbnNlSWQiOiI1NlpTNVBRMVJGIiwibGljZW5zZWVOYW1lIjoi5q2j54mI5o6I5p2 ...

  5. C#获取当前堆栈的各调用方法列表

    在使用.NET编写的代码在debug时很容易进行排查和定位问题,一旦项目上线并出现问题的话那么只能依靠系统日志来进行问题排查和定位,但当项目复杂时,即各种方法间相互调用将导致要获取具体的出错方法或调用 ...

  6. Linux umask

    新建一个文件或目录,它的默认权限是什么?如果要修改一个用户创建的文件和目录的默认权限该如何做?本文将介绍相关的内容.说明:本文的演示环境为 ubuntu 16.04. 文件的默认权限 为了查看用户创建 ...

  7. centos下部署redis服务环境及其配置说明

    Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工作由VMware主 ...

  8. 常用rsync命令操作梳理

    作为一个运维工程师,经常可能会面对几十台.几百台甚至上千台服务器,除了批量操作外,环境同步.数据同步也是必不可少的技能.说到“同步”,不得不提的利器就是rsync.rsync不但可以在本机进行文件同步 ...

  9. Spring RPC 入门学习(2)-获取Map对象

    Spring RPC传递Map用例编写 1. 新建RPC接口类 package com.cvicse.ump.rpc.interfaceDefine; import java.util.Map; pu ...

  10. QT 窗口置顶功能

    Qt中,保持窗口置顶的设置为: Qt::WindowFlags m_flags = windowFlags(); setWindowFlags(m_flags | Qt::WindowStaysOnT ...