打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
NOIP训练营集训笔记-状态压缩DP


NOIP训练营集训笔记-动态规划Part1

NOIP训练营集训笔记-动态规划Part1(续)

NOIP训练营集训笔记-动态规划Part2(上)路径行走问题

点击一下标题可查看其他相关笔记!

清北学堂(提高组精英班)集训笔记——图论(上)

清北NOIP训练营集训笔记——图论(下)(提高组精英班)

NOIP训练营集训笔记—信息学基础算法(倍增与分治算法)

NOIP训练营集训笔记—信息学基础算法(贪心和搜索算法)

NOIP训练营集训笔记—基本数论(一)

NOIP训练营集训笔记—基本数论(二)

NOIP训练营集训笔记—基本数论之代数

NOIP训练营集训笔记-Trie树,KMP算法

NOIP训练营集训笔记-AC自动机,对拍,随机算法

NOIP训练营集训笔记-过程型状态划分习题解析

NOIP训练营集训笔记-动态规划 二维平面及区间动态规划

NOIP训练营集训笔记-序列型状态划分

温馨提示:大家好,由于OI交流群(群号283478320)进群人数众多,容纳人数有限,目前3000人已满,如果1群满员的情况下,我们已经开通OI交流群2群(群号:549859236),供大家交流使用。有兴趣的同学和家长也可以到2群交流分享,把经验和知识分享给更多的同学。我们会选择一些活跃的同学或家长作为2群的管理员。谢谢大家~

NOIP训练营笔记

 状态压缩DP:

* 状态压缩 DP:利用位运算来记录状态,并实现动态规划。
* 问题特点:数据规模较小;不能使用简单的算法解决。

* 给定两个布尔变量a,b,他们之间可进行布尔运算:
* 与运算 and:当 a,b 均为真的时候, 与 b为真;
* 或运算 or:当 a,b 均为假的时候, 或 b为假;
* 非运算 not:当 a 均为真的时候, 非 a为假;
* 异或运算 xor:当 a,b 不是同时真,或者同时假的时候, 异或 b为真。

* 我们把 0 视作布尔的假,1 视作布尔的真,则整数亦存在二进制的运算,而运算的结果则是二进制里对应的位作相应的布尔操作。(按位运算)
* (23)10 = (10111)2,(10)10 = (1010)2
* 与运算 and: 23 and 10 = (10)2 = 2
* 或运算 or: 23 or 10 = (11111)2 = 31
* 异或运算 xor: 23 xor 10 = (11101)2 = 29

* 另外,在整数位运算中还有位移操作:
* (23)10 = (10111)2,(10)10 = (1010)2
* 左移 «:将整个二进制向左移动若干位,并用 0 补充;
- 10 << 1 = (10100)2 = 20(左移n位=乘以2n
* 右移 »:将整个二进制向右移动若干位(实际最右边的几位就消失了);
- 23 >> 2 = (101)2 = 5(右移n位=除以2n

位运算比加减乘除都快!

经典例题:玉米田(Luogu 1879)

  农场主 John 新买了一块长方形的新牧场,这块牧场被划分成 M 行 N列 (1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。 John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
  遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是 John 不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
  John 想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
- 1 1 1
- 0 1 0 (1 代表这块土地适合种草)
- 总方案数: 9
* 问题限制:没有哪两块草地有公共边
* 考虑动态规划:如何划分状态?
- 从上到下,从左到右?
- 记录 F[i][j] 为 (i,j) 选择在这个位置种草的方案数?
- 问题:如何转移?我们只能限制左边的位置不能种草;不能限制上面的位置不能种草,所以这样不可行!
* 划分状态:考虑用行来划分状态; 第 i 行的状态只取决于第 i-1 行。

* 状态表示:如何表示一行的种草状态?
* 二进制位:将一整行看作是一个大的二进制数,其上为 1 表示种了草, 0表示没有种草,最好不用二维数组,因为行列数目不定,不好开数组。
* 这样,我们可以用一个数来表示一行的种草状态;
例如:下图中,在一行的第一个位置和第四个位置种草,可以用18来表示这种状态:

* 状态设计:设 F[i][j] 为已经处理到第 i 行,且第 i 行的状态是 j,的总方案数。 (其中 j 为一个大的二进制数)
* 状态转移:枚举前一行的种草状态 k,如果没有产生冲突,则加上:
F[i][j] = ∑ F[i − 1][k]
* 问题:如何判断两个状态是否冲突? 如何运用二进制运算?

* 解决方案:采取与运算;如果两个状态是冲突的,则肯定有一位上都为1, and 的结果肯定不为 0;否则若结果为 0,则代表状态不冲突。

* 下一个问题:如何判断当前状态j是合法的?

1 没有占有障碍物:和判断两个状态是否冲突是类似的。
2 左右位置判断:与上一行判断冲突,我们只考虑到了上下位置不可能同时种草,但还没有考虑左右的情况。
- 解决方案: j & (j«1) = 0 且 j & (j»1) = 0(我只想说:左右两个相互叠加判断,真TM聪明!)

//T31:玉米田(状态压缩DP)

const int N=13;//边长

const int S=(1<<N)+5;//二进制状态数=213,+5的目的是让心里有个安慰~应该不会爆炸吧O(∩_∩)O,开大点数组这样

int a[N][N];//棋盘数组

int s[N];//每一行空地的状态

int f[N][S];//动态规划数组

int g[S];//标记一个状态是否合法

//主过程

ts=1<<m;//总状态数

for(int i=0;i<ts;++i)//判断左右位置

    g[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);//左右位置是否有冲突

//T31:玉米田(状态压缩DP)

f[0][0]=1;//初值!

for(int i=1;i<=n;++i)//枚举行

    for(int j=0;j<ts;++j)//枚举这行的状态

        if(g[j]&&((j&s[i])==j))//状态本身合法且不占障碍物

        {

            for(int k=0;k<ts;++k)//枚举上一行的状态

                if((k&j)==0)//如果不冲突

                    f[i][j]=(f[i][j]+f[i-1][k])%MOD;

        } 

int ans=0;

for(int j=0;j<ts;++j)

    ans=(ans+f[n][j])%MOD;//答案加起来

//T31:玉米田(状态压缩DP)

cin>>n>>m;//读入棋盘状态

for(int i=1;i<=n;++i)

    for(int j=1;j<=m;++j) 

        cin>>a[i][j];

for(int i=1;i<=n;++i)//预处理每一行的状态

    for(int j=1;j<=m;++j)

        s[i]=(s[i]<<1)+a[i][j];

经典例题:互不侵犯(Luogu 1896)

* 在 N×N 的棋盘里面放 K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 个格子。
- 3 2 (在 3×3 的棋盘内放置 2 个国王)
- 总方案数: 16

* 状态设计:设 F[i][j][k] 为已经处理到第 i 行,且第 i 行的状态是j,现在一共放置了 k 个国王的总方案数。 (其中 j 为一个大的二进制数)
* 状态转移:枚举前一行的放置国王状态为 l,如果没有产生冲突,则加上(其中 x 为状态 j 的国王数)
F[i][j][k] = ∑ F[i − 1][l][k − x]
* 问题:如何判断两个状态是否冲突? 如何运用二进制运算?

//T32:互不侵犯(状态压缩DP)

const int N=10;

const int S=(1<<N)+5;//二进制状态数

int cnt[S];//记录一个状态有多少个1(按照这个状态放国王,一共有多少个国王) 

int g[S];//记录一个状态是否合法

int h[S];

LL f[N][S][N*N];//动态转移状态->[行][状态][一共摆了多少个国王]

//T32:互不侵犯(状态压缩DP)

ts=1<<n;

for(int i=0;i<ts;++i)

{

    g[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);

    h[i]=i|(i<<1)|(i>>1);//h:一个状态把它左右都占据之后的状态(h[i]表示第i个国王把他左右旁边占领之后,标记为1)

    cnt[i]=cnt[i>>1]+(i&1);//计算多少个1((i&1)表示最后一个是1就++) 

}

//T32:互不侵犯(状态压缩DP)

f[0][0][0]=1;//初值

//顺推

for(int i=0;i<n;++i)//行数

    for(int j=0;j<ts;++j)//这一行状态

        for(int l=0;l<=k;++l) if(f[i][j][l])//枚举个数

            for(int p=0;p<ts;++p)//枚举下一行

                if(g[p]&&((h[p]&j)==0))//如果是合法状态,且不冲突(p是一个合法状态,p国王占的领地和j是没有冲突的)    

                {

                    f[i+1][p][l+cnt[p]]+=f[i][j][l];

                }         

LL ans=0;//答案

for(int j=0;j<ts;++j) ans+=f[n][j][k];//所有的状态都能成为答案

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
同或&&异或
干货|十分钟教你用动态规划算法解Travelling Salesman Problem(TSP)问题...
位操作运算的奇技淫巧!(附源码)
按位异或运算符 ^
位运算之N皇后和Gray码
计算机基础|你知道汇编语言吗?
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服