Acwing 状态压缩 DP
最编程
2024-10-07 17:11:37
...
Acwing 291.蒙德里安的梦想
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
实现思路:先放横着的,再放竖着的。
总方案数,等于只放横着的小方块的合法方案数。(放完横着的方块之后,竖着的只能被动填充进去,不需要进行额外进行竖着的情况)
- 方案合法的条件是:当横着的方块放完后,竖着的小方块恰好能把剩余的地方全部填满
- 那如何判断方案是否合法呢?即怎么看竖着的小方块是否能把剩余部分填满呢?
- 因为是竖着放的,所以可以按列来看,每一列的内部,只要所有连续的空余小方块的个数为偶数即可。
- 状态表示:f[i][j]
表示前
i-1列已经摆好,且从第
i-1列是否有伸出一格到第
i列的情况(这个情况用
j`表示)的所有方案数。- 对于j:j是一个二进制数,位数与棋盘的行数相等,比如N=5,M=5的棋盘,j为5位的二进制数(XXXXX)_2,但在程序中还是用十进制数表示。(所以要用位运算来判断某一位是1或0)
- 什么叫
j
表示从第i-1
列伸出一格到第i
列的情况?如下图,第i
列的第1,2,5
个格子,是从i-1
列伸过来的。此时的状态j
为 (11001)_2,即对于第i
列的所有格子,第1,2,5
个格子被伸出来占据了(j
是个二进制数,若该列的某一行,有被前面的列伸出来一格,则用1
表示,否则用0
表示),那么这些个位置被侵占了,就不能再横放了。 - 这样对
f[i][j]
更通俗的理解就是第i
列的横放情况,由j
的二进制表示出第i
列哪些行可以横放
- 状态转移:既然第
i
列固定了,我们需要看 第i-2
列是怎么转移到到第i-1
列的(看最后转移过来的状态)。假设此时对应的状态是k
(第i-2
列到第i-1
列伸出来的二进制数,比如00100),k
也是一个二进制数,1表示哪几行小方块是横着伸出来的,0表示哪几行不是横着伸出来的。它对应的方案数是f[i−1,k]
,即前i-2
列都已摆完,且从第i-2
列伸到第i-1
列的状态为k
的所有方案数。 - 那
k
需要满足什么条件,才能够从f[i - 1][k]
转移到f[i][j]
呢?- ①k和j不能冲突,也就是第
i-1
列和第i
列的不能有重叠的1,即两列的相同行不能同时为1(不能同时有前一列伸过来的)。如下图,在第一行k的二进制位为1,j的二进制位也为1,那么第i列就不能再横放东西了。转化为代码判断就是**(k & j ) ==0
** ,表示两个数相与,如果两有对应位同时为1,则相与结果为1,否则为0即没有冲突。
- ②既然从第
i-1
列到第i列横着摆的,和第i-2
列到第i-1
列横着摆的都确定了,那么第i-1
列 空着的格子就确定了,这些空着的格子将来用作竖着放。如果 某一列有这些空着的位置,那么该列所有连续的空着的位置长度必须是偶数(即不能有奇数个0)。设置一个st[]数组表示的是这一列连续0的个数的情况,若为true表示连续偶数个0(合法状态),否则为false。体现到代码判断第i-1
列满足竖放的合法状态:st[k|j]=true
,- 解释:第i-1列中1的总个数,就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的
- st[k]表明第i-2列插入到i-1列的1的个数,i-1列被动生成的1
- st[j]表明第i-1列插入到i列的1的个数,也就是i-1列自己生成的1
- 解释:第i-1列中1的总个数,就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的
- ①k和j不能冲突,也就是第
- 最后结果输出
f[m][0]
- 对于
f[0][0]=1
和最后输出f[m][0]
理解:数组下标从0开始表示实际棋盘的第一列,放f[][]
表示当前列的横放情况,f[0][0]
表示当前一列没有伸过来的(因为前面根本没有列),所以也就没有横放的情况,只有竖放的情况,即f[0][0]=1
。所以输出f[m][0]
表示第m列不横放,这也是合理的,如果第m列横放了,那就超出m列的范围了。
- 对于
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 12,M = 1 << N;
long long f[N][M];
bool st[M];//记录当前连续0的个数是否为偶数 ,即合法状态为true
int n,m;
int main(){
while(cin >> n >> m, n || m){
memset(f,0,sizeof f);
//先预处理得到每一列连续0的个数 是偶数还是奇数
for(int i = 0 ; i < 1 << n ; i ++){
st[i] = true;
int cnt = 0;//记录0的个数
for(int j = 0; j < n ; j ++){//遍历当前列的每一行
if(i >> j & 1){//取得当前列的一位 若为1
//判断之前记录的0的个数 是否为奇数
if(cnt & 1) st[i] = false;//若为奇数 标记为不合法
}
else cnt ++;//取得当前行的一位 为0
}
//得到该列0个数的最终结果 判断是否合法
if(cnt & 1) st[i] = false;
}
f[0][0] = 1;
//开始DP
for(int i = 1 ; i <= m ; i ++){//从列开始
for(int j = 0 ;j < 1 << n ; j ++){//第i列情况
for(int k = 0 ; k < 1 << n ; k ++){//第i-1列情况
if((j & k) == 0 && st[j | k])//若没有冲突 且连续0的个数为偶数 | 按位或
f[i][j] += f[i - 1][k];//合法
}
}
}
cout << f[m][0] << endl;
}
return 0;
}
Acwing 91.最短Hamilton路径
实现思路:
-
f(i,j)
表示从0号点到j
号点,且中间经过点的状态是i
(和上题类似,用二进制表示,位为1就表示某点经过,同样要进行位运算来获得每一位。如 1 1 1 0 1 表示第0,1,2,4被走过)的最短路径长度。 - 集合划分:最后一个点是
j
,按倒数第二个点划分,若倒数第二个点是k
,即从0号点经过一系列不重复的点到达点k
,再经过点k
到达终点j
。同时在这个状态i中就不能再包括点j了(因为路径要不重不漏),要再状态i中除去j。 - 最终状态转移方程为:
f[i][j]=min(f[i][j],f[i-1<<j][k]+a[k][j])
;因为i
是二进制数,除去j
点就表示使j
位置上的二进制位由0后改为1,即相减。
具体实现代码(详解版):
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 21,M = 1 << N;
int n;
int f[M][N],a[N][N];
int main(){
cin >> n;
for(int i = 0 ; i < n; i ++){
for(int j = 0 ; j < n ; j ++){
cin >> a[i][j];
}
}
memset(f,0x3f,sizeof f);//初始化图上距离为无穷大
f[1][0] = 0;//从0开始到0本身的距离为0
for(int i = 1 ; i < 1 << n ; i ++){
for(int j = 0 ; j < n ; j++){//枚举0到n号点
if(i >> j & 1){//为1要到达点j
for(int k = 0 ; k < n ; k ++){//枚举倒数第二个点k
if((i - (1 << j)) >> k & 1)//如果k在路径中
f[i][j] = min(f[i][j],f[i - (1 << j)][k] + a[k][j]);
}
}
}
}
//从0经过所有点 到达n - 1号点
cout << f[(1 << n) - 1][n - 1];
return 0;
}
这两道题目是竞赛题,难度有点大。就在此记录一下把~
推荐阅读
-
Acwing 状态压缩 DP
-
汽车后面的字母是什么意思?-增压发动机 类型一:TSI 大众的TSI在国内外有着不一样的意思,国外的意思是Twincharger Stratified ion,指双增压(涡轮和机械增压)分层喷射技术。而国内的意思,T代表涡轮增压,Si代表燃油直喷,而不是T与FSI的简称,并没有燃油分层喷射技术,因为国内燃油质量一般,达不到分层喷射的要求。 在国内,我们经常会看到不同的TSI标志。有全红的、有就“SI”是红的、还有只有“I”是红的。但大家别误会他们技术不一样,这只是为了区分不同的排量而已。例如:2.0排量和1.8排量为“SI”是红色的,而2.0TSI车型中的高配车型或者高端车型则使用全红的标识,那么1.4排量的当然只能是只有“I”是红色的了。 类型二:TFSI TFSI发动机也是涡轮燃油直喷发动机它可以说是FSI发动机和涡轮增压器的结合。即涡轮增压(Turbocharger)+FSI。它的T和TSI中的T一样,表示采用涡轮增压技术,后面的FSI即燃油分层喷射发动机(Fuel Stratified ion),S表示“分层次的”。TFSI发动机既分层喷射,又有涡轮增压,是TSI发动机的升级版。 类型三:TDI TDI是英文Turbo Direct ion的缩写,意为涡轮增压直接喷射柴油发动机。 为了解决SDI(自然吸气式柴油发动机)的先天不足,人们在柴油机上加装了涡轮增压装置,使得进气压力大大增加,压缩比一般都到10以上,这样就可以在转速很低的情况下达到很大的扭矩,而且由于燃烧更加充分,排放物中的有害颗粒含量也大大降低。TDI技术使燃油经由一个高压喷射器直接喷射入气缸,因为活塞顶地造型是一个凹陷式的碗状设计,燃油会在气缸内形成一股螺旋状的混合气。 自然吸气发动机类型一:CGI/CDI 发动机CGI技术是一种奔驰公司开发的缸内直喷技术。供油动作已完全独立于进门与活塞系统之外,ECU也因而拥有更多的主导权。超乎传统喷射理论的稀薄燃烧与更多元的混合比便得以实现。在稳定行进或低负载状态下,采用缸内直喷设计的发动机得以进入Ultra lean(精实)模式。 在此设定下,发动机于进气行程时只能吸进空气,至于喷油嘴则在压缩行程才供给燃料,以达到节约的效果。根据实际测试,其最高能达到1:65的油、气比例,除了节能表现相当惊人,整体动力曲线也能够维持相当高的平顺度。而CDI则为该技术的柴油版本。类型二:VVT/CVVT/VVT-I/MIVEC/VTEC/i-VTEC 发动机可变气门正时技术(VVT,Variable Valve Timing)原理是根据发动机的运行情况,调整进气(排气)的量,和气门开合时间、角度,使进入的空气量达到最佳,提高燃烧效率。优点是省油,功升比大而缺点是中段转速扭矩不足。 目前本田的VTEC、i-VTEC、;丰田的VVT-i;日产的CVVT;三菱的MIVEC;铃木的VVT;现代的VVT;起亚的CVVT;江淮的VVT;长城的VVT等也逐渐开始使用。总的说来其实就是一种技术,名字不同。 但部分车型仅具有可变气门技术而没有正时技术,虽然比一般发动机要省油,但依然赶不上带正时技术的发动机。绿色发动机 类型一:Hybrid
-
[回溯状态压缩深度优先] 37.解数独
-
NeurIPS 2022 | 最强斗地主AI!网易互娱AI Lab提出基于完美信息蒸馏的方法-完美信息蒸馏(PTIE) 在斗地主游戏中,非完美信息的引入主要是由于三位玩家均不能看到别人的手牌,对于任意一位玩家而言,仅可知道其余两位玩家当前手牌的并集,而难于精准判断每位玩家当前手牌。完美信息蒸馏的思路是针对这种非完美问题,构建一个第三方角色,该角色可以看到三位玩家的手牌,该角色在不告知每位玩家完美信息的情况下通过信息蒸馏的方式引导玩家打出当前情况下合理的出牌。 以强化学习常用的 Actor-Critic 算法为例,PTIE 在 Actor-Critic 算法的应用中可以利用 Critic 的 Value 输出作为蒸馏手段来提升 Actor 的表现。具体而言即在训练中 Critic 的输入为完美信息(包含所有玩家的手牌信息),Actor 的输入为非完美信息(仅包含自己手牌信息),此种情况下 Critic 给予的 Value 值包含了完美信息,可以更好地帮助 Actor 学习到更好的策略。 从更新公式上来看,正常的 Actor-Critic 算法 Actor 更新的方式如下: 在 PTIE 模式下,对于每个非完美信息状态 h,我们可以在 Critic 中构建对应的完美信息状态 D(h),并用 Critic 的输出来更新 Actor 的策略梯度,从而达到完美信息蒸馏的效果。 PTIE 框架的整体结构如下图所示: 无论是训练还是执行过程中智能体都不会直接使用完美信息,在训练中通过蒸馏将完美信息用于提升策略,从而帮助智能体达到一个更高的强度。 PTIE 的另一种蒸馏方式是将完美信息奖励引入到奖励值函数的训练中,PerfectDou 提出了基于阵营设计的完美信息奖励 node reward,以引导智能体学习到斗地主游戏中的合作策略,其定义如下: 如上所示,完美信息部分 代表 t 时刻地主手牌最少几步可以出完,在斗地主游戏中可以近似理解为是距游戏获胜的距离, 代表 t 时刻地主阵营和农民阵营距游戏获胜的距离之差, 为调节系数。通过此种奖励设计,在训练时既可以一定程度地引入各玩家的手牌信息(出完的步数需要知道具体手牌才能计算),同时也鼓励农民以阵营的角度做出决策,提升农民的合作性。 特征构建: PerfectDou 针对牌类游戏的特点主要构建了两部分特征:牌局状态特征和动作特征。其中牌局状态特征主要包括当前玩家手牌牌型特征、当前玩家打出的卡牌牌型特征、玩家角色、玩家手牌数目等常用特征,动作特征主要用于刻画当前状态下玩家的所有可能出牌,包括了每种出牌动作的牌型特征、动作的卡牌数目、是否为最大动作等特征。 牌型特征为 12 * 15 的矩阵,如下图所示: 该矩阵前 4 行代表对应每种卡牌的张数,5-12 行代表该种卡牌的种类和对应位置。 网络结构和动作空间设计 针对斗地主游戏出牌组合数较多的问题,PerfectDou 基于 RLCard 的工作上对动作空间进行了简化,对占比最大的两个出牌牌型:飞机带翅膀和四带二进行了动作压缩,将整体动作空间由 27472 种缩减到 621 种。 PerfectDou 策略网络结构如下图所示: 策略网络结构同样分为两部分:状态特征部分和动作特征部分。 在状态特征部分,LSTM 网络用于提取玩家的历史行为特征,当前牌局状态特征和提取后的行为特征会再通过多层的 MLP 网络输出当前的状态信息 embedding。 在动作特征部分,每个可行动作同样会经过多层 MLP 网络进行编码,编码后的动作特征会与其对应的状态信息 embedding 经过一层 MLP 网络计算两者间的相似度,并经由 softmax 函数输出对应的动作概率。 实验结果
-
UVA11806: 容差原理 + 状态压缩 - 代码:
-
[动态编程] [状态压缩] [2 个选择] [广度搜索] 1494.并行课程 II
-
寻找可变回文路径:使用深度优先搜索和树状结构的状态压缩法解决第2791题
-
搞定BZOJ 2669:[cqoi2012]局部最小值问题(用状态压缩和容斥原理解决)
-
动态规划(单状态dp+多状态dp)、贪心算法 2019-09-30(未经允许禁止转载)