状态压缩

合集下载

动态规划之状态压缩

动态规划之状态压缩

状态压缩Abstract信息学发展势头迅猛,信息学奥赛的题目来源遍及各行各业,经常有一些在实际应用中很有价值的问题被引入信息学并得到有效解决。

然而有一些问题却被认为很可能不存在有效的(多项式级的)算法,本文以对几个例题的剖析,简述状态压缩思想及其应用。

Keywords状态压缩、Hash、动态规划、递推ContentIntroducti o n作为OIers,我们不同程度地知道各式各样的算法。

这些算法有的以O(logn)的复杂度运行,如二分查找、欧几里德GCD算法(连续两次迭代后的余数至多为原数的一半)、平衡树,有的以)运行,例如二级索引、块状链表,再往上有O(n)、O(n p log q n)……大部分问题的算法都有一个多项式级别的时间复杂度上界1,我们一般称这类问题2为P (deterministic Polynomial-time)类问题,例如在有向图中求最短路径。

然而存在几类问题,至今仍未被很好地解决,人们怀疑他们根本没有多项式时间复杂度的算法,NPC(NP-Complete)和NPH(NP-Hard)就是其中的两类,例如问一个图是否存在哈密顿圈(NPC)、问一个图是否不存在哈密顿圈(NPH)、求一个完全图中最短的哈密顿圈(即经典的Traveling Salesman Problem货郎担问题,NPH)、在有向图中求最长(简单)路径(NPH),对这些问题尚不知有多项式时间的算法存在。

P和NPC都是NP(Non-deterministic Polynomial-time)的子集,NPC则代表了NP类中最难的一类问题,所有的NP类问题都可以在多项式时间内归约到NPC问题中去。

NPH包含了NPC和其他一些不属于NP(也更难)的问题,NPC问题的函数版本(相对于判定性版本)一般是NPH的,例如问一个图是否存在哈密顿圈是NPC的,但求最短的哈密顿圈则是NPH的,原因在于我们可以在多项式时间内验证一个回路是否真的是哈密顿回路,却无法在多项式时间内验证其是否是最短的,NP类要求能在多项式时间内验证问题的一个解是否真的是一个解,所以最优化TSP问题不是NP的,而是NPH的。

一道状态压缩DP的详细思路

一道状态压缩DP的详细思路

一道状态压缩DP的详细思路难易指数:★★★河北省衡水中学高亚在动态规划的过程中,状态的表示有时候很是恶心,不容易表示出来,所以,我们需要用一些编码技术,将这些状态表示出来,最常用的方法是用一个二进制数来表示一个集合的状态,下面通过一道例题来对状态压缩DP进行分析一、题目问题描述小keke同学非常喜欢玩俄罗斯方块(**),他最近发现传统的俄罗斯方块很无趣,于是他想到了一个新规则的游戏来考验你。

游戏是这样的:给定你一个宽度为w的游戏场地,我们设高度为正无穷。

现在给你3种俄罗斯方块:1*2的方块2*2的方块2*2的方块去掉一个1*1的方块如果你明白俄罗斯方块的规则的话,方块在下落过程中是可以随便旋转的。

而且是从上往下落,上面的落在下面的上面。

现在给定你一个高度h,让你求出有多少种游戏的方法,使得最后恰好落满h的高度(最上层是齐平的)。

因为这样可以得很多很多分。

输入数据两个整数h,w含义如题所述输出数据一个整数,为能达到要求的游戏方法的总数。

样例输入2 2样例输出3数据范围1<=h,w<=9,注意答案有可能很大(你懂得,用不到高精度)二、关于状态压缩的思路首先,先根据题意,将这个俄罗斯方块的所有形状都画出来(注意这些方块已经标上号了,下面直接引用不加说明)观察到只有七种情况,并且它们的高度只有两行,所以,一个俄罗斯方块最多只会影响上一行或下一行,而不会影响其他行题目中给出的高度,宽度最多只有9,用二进制位完全可以满足要求,所以可以用0表示这个地方是空的,1表示这个地方已经被填充了木块,那么可以很方便的用两个十进制数来表示两行的状态设f[i][j]表示前i行,并且第i行状态为j的最多方案数,那么因为第i行的放置方案仅仅只会影响上一行,所以可以得以下方程:f[i][j]=∑f[i−1][j′]其中,要求第i-1行的j'状态能够推到j这个状态来个最简单的例子,一个2*2的方格,从空的状态到把它填满,方案数是多少?(其实就是样例)可以看到,如果用刚才的方法,直接用0表示不填,用1表示填,那么这三种方案的状态表示是一样的,所以如果只是简单的求和,方案数就只是简单的1,并不是我们想要的结果,如何满足这一点的要求呢?其实可以采用预处理的方法,预处理出从状态i到状态j的方案数有多少种,记录到g[i][j]数组中(如果不能转移到,那么g[i][j] = 0),然后在方程转移的时候,直接采用下面的方程:f[i][j]=∑g[j′][j]∗f[i−1][j′]其中j'还是老规矩,能从j'状态推到j这个状态因为有了g[j'][j]这个从j'状态推到j状态的方案数,所以在i-1行状态为j'时,想推到第i行状态为j,共有g[j'][j]种方案可以选择,根据乘法原理,所以f[i-1][j']的方案数乘以g[j'][j]就可以得到f[i][j],最后针对每种状态,求和即可好了,我主要想说的是如何进行预处理,最后还想说说方程中f数组的初值问题。

枚举法的四种方法 -回复

枚举法的四种方法 -回复

枚举法的四种方法-回复标题:枚举法的四种方法- 探索实用解决问题的道路引言:在计算领域中,枚举法是一种常见且实用的解决问题的方法。

它通过列举出所有可能的情况进行全面地搜索,以找到问题的最优解。

本文将详细介绍枚举法的四种方法,包括穷举法、位图法、状态压缩法和子集枚举法,以及它们在实际问题中的应用。

一、穷举法穷举法是最简单直观的枚举方法,它通过遍历所有可能的情况来解决问题。

它的基本思想是从问题的定义出发,按一定规则生成所有可能的解,并逐一验证每个解是否满足问题的条件。

例如,在解决数字组合问题中,可以通过循环嵌套的方式枚举出所有可能的数字组合,并判断其是否满足特定条件。

二、位图法位图法是一种对状态进行二进制压缩的枚举方法。

它通过使用一个二进制位图来表示问题中的状态,其中每个位表示对应状态的存在与否。

利用位运算的特性,可以高效地进行状态的枚举和计算。

例如,在解决集合运算问题时,可以使用位图法来表示集合的子集关系,通过遍历位图的所有可能状态来解决问题。

三、状态压缩法状态压缩法是一种将问题的状态进行压缩表示的枚举方法。

它通过将问题的状态映射为一个整数或一个较小的数据结构,来减少存储和计算的复杂度。

状态压缩法常用于解决动态规划等需要存储大量状态信息的问题。

例如,对于旅行商问题,可以使用状态压缩法将所有已经访问过的城市用一个二进制数表示,然后通过枚举所有可能的状态来求解最优路径。

四、子集枚举法子集枚举法是一种通过枚举原问题的所有子集来解决问题的方法。

它的基本思想是从原问题的解出发,逐步取出元素或排列组合来生成子问题的解,并递归地处理子问题。

子集枚举法常应用于组合数学和图论等领域的问题。

例如,在解决组合问题时,可以使用子集枚举法来生成所有可能的组合,并进行后续的计算和判断。

结论:枚举法是一种解决问题的通用方法,可以应用于多个领域和类型的问题。

通过穷举法、位图法、状态压缩法和子集枚举法这四种方法,可以高效地解决各种实际问题。

状压DP

状压DP
【POJ1185】炮兵阵地--经典状压DP
类似于上面一道题,一个方格组成的矩阵(N*M, N<=100, M<=10),每个方格可 以放大炮用P表示,不可以放大炮用H表示,让放最多的大炮。大炮与大炮间不会互 相攻击。(图示为大炮的攻击范围)
解题思路: dp[i][j][k] 表示第i行状态为k,第i-1行状态为j时的最大炮兵个数 dp[i][k][t] =max(dp[i][k][t],dp[i-1][j][k]+num[t]); num[t]为t状态中1的个数 其中 (t&j)==0, (t&k)==0
状压DP
判断一行是否有相邻距离小于3的1 1.bool ok(int x){ 2. if(x&(x<<1)) return 0; 3. if(x&(x<<2)) return 0; 4. return 1; 5.}
练习地址: /vjudge/contest/vie w.action?cid=124250#overview

பைடு நூலகம்据规模比较小,可以进行可行的压缩。
状压DP
状态的表示:位压缩 比如10个灯,用 2^10 分别表示这10个灯亮还是不亮 3进制、4进制。。。。。 位运算的引入 按位与运算 (and) 效果是 全一为一,反之为零。 按位或运算 (or) 效果是 有一为一,反之为零。 按位异或运算 (xor) 效果是 不同为一,反之为零。
状压DP
状压DP
状态压缩动态规划跟普通的动态规划其实没啥区别,就是状态表示不一样 一个用数组直接做状态,一个用int值做状态 状态压缩的动态规划解决的就是那种状态很多,不容易用一般的方法表示的动态规 划问题

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题基于连通性状态压缩的动态规划问题基于状态压缩的动态规划问题是⼀类以集合信息为状态且状态总数为指数级的特殊的动态规划问题.在状态压缩的基础上,有⼀类问题的状态中必须要记录若⼲个元素的连通情况,我们称这样的问题为基于连通性状态压缩的动态规划问题,本⽂着重对这类问题的解法及优化进⾏探讨和研究.本⽂主要从动态规划的⼏个步骤——划分阶段,确⽴状态,状态转移以及程序实现来介绍这类问题的⼀般解法,会特别针对到⽬前为⽌信息学竞赛中涌现出来的⼏类题型的解法作⼀个探讨.结合例题,本⽂还会介绍作者在减少状态总数和降低转移开销两个⽅⾯对这类问题优化的⼀些⼼得.总结⾃序⾔先看⼀个⾮常经典的问题——旅⾏商问题(即TSP问题,Traveling Salesman Problem):⼀个n(≤15)个点的带权完全图,求权和最⼩的经过每个点恰好⼀次的封闭回路.这个问题已经被证明是NP完全问题,那么对于这样⼀类⽆多项式算法的问题,搜索算法是不是解决问题的唯⼀途径呢? 答案是否定的.不难发现任何时候我们只需要知道哪些点已经被遍历过⽽遍历点的具体顺序对以后的决策是没有影响的,因此不妨以当前所在的位置i,遍历过的点的集合S为状态作动态规划:动态规划的时间复杂度为,虽然为指数级算法,但是对于n = 15的数据规模来说已经⽐朴素的的搜索算法⾼效很多了.我们通常把这样⼀类以⼀个集合内的元素信息作为状态且状态总数为指数级别的动态规划称为基于状态压缩的动态规划或集合动态规划.基于状态压缩的动态规划问题通常具有以下两个特点:1.数据规模的某⼀维或⼏维⾮常⼩;2.它需要具备动态规划问题的两个基本性质:最优性原理和⽆后效性.⼀般的状态压缩问题,压缩的是⼀个⼩范围内每个元素的决策,状态中元素的信息相对独⽴.⽽有些问题,仅仅记录每个元素的决策是不够的,不妨再看⼀个例⼦:给你⼀个m * n (m,n≤9) 的矩阵,每个格⼦有⼀个价值,要求找⼀个连通块使得该连通块内所有格⼦的价值之和最⼤.按从上到下的顺序依次考虑每个格⼦选还是不选,下图为⼀个极端情况,其中⿊⾊的格⼦为所选的连通块.只考虑前5⾏的时候,所有的⿊⾊格⼦形成了三个连通块,⽽最后所有的⿊⾊格⼦形成⼀个连通块.如果状态中只单纯地记录前⼀⾏或前⼏⾏的格⼦选还是不选,是⽆法准确描述这个状态的,因此压缩的状态中我们需要增加⼀维,记录若⼲个格⼦之间的连通情况.我们把这⼀类必须要在状态中记录若⼲个元素之间的连通信息的问题称为基于连通性状态压缩的动态规划问题.本⽂着重对这类问题进⾏研究.连通是图论中⼀个⾮常重要的概念,在⼀个⽆向图中,如果两个顶点之间存在⼀条路径,则称这两个点连通.⽽基于连通性状态压缩的动态规划问题与图论模型有着密切的关联,⽐如后⽂涉及到的哈密尔顿回路、⽣成树等等.通常这类问题的本⾝与连通性有关或者隐藏着连通信息.全⽂共有六个章节.第⼀章,问题的⼀般解法,介绍解决基于连通性状态压缩的动态规划问题的⼀般思路和解题技巧;第⼆章,⼀类简单路径问题,介绍⼀类基于棋盘模型的简单路径问题的状态表⽰的改进——括号表⽰法以及提出⼴义的括号表⽰法;第三章,⼀类棋盘染⾊问题,介绍解决⼀类棋盘染⾊问题的⼀般思路;第四章,⼀类基于⾮棋盘模型的问题,介绍解决⼀类⾮棋盘模型的连通性状态压缩问题的⼀般思路;第五章,⼀类最优性问题的剪枝技巧,本章的重点是优化,探讨如何通过剪枝来减少扩展的状态的总数从⽽提⾼算法的效率;第六章,总结,回顾前⽂,总结解题⽅法.⼀. 问题的⼀般解法基于连通性状态压缩的动态规划问题通常具有⼀个⽐较固定的模式,⼏乎所有的题⽬都是在这个模式的基础上变形和扩展的.本章选取了⼀个有代表性的例题来介绍这⼀类问题的⼀般解法.【例1】Formula 1问题描述给你⼀个m * n的棋盘,有的格⼦是障碍,问共有多少条回路使得经过每个⾮障碍格⼦恰好⼀次.m, n ≤ 12.Ural1519, Timus Top Coders : Third Challenge如图,m = n = 4,(1, 1), (1, 2)是障碍,共有2条满⾜要求的回路.算法分析【划分阶段】这是⼀个典型的基于棋盘模型的问题,棋盘模型的特殊结构,使得它成为连通性状态压缩动态规划问题最常见的“舞台”.通常来说,棋盘模型有三种划分阶段的⽅法:逐⾏,逐列,逐格.顾名思义,逐⾏即从上到下或从下到上依次考虑每⼀⾏的状态,并转移到下⼀⾏;逐列即从左到右或从右到左依次考虑每⼀列的状态,并转移到下⼀列;逐格即按⼀定的顺序(如从上到下,从左到右)依次考虑每⼀格的状态,并转移到下⼀个格⼦.对于本题来说,逐⾏递推和逐列递推基本类似,接下来我们会对逐⾏递推和逐格递推的状态确⽴,状态转移以及程序实现⼀⼀介绍.有的题⽬, 逐⾏递推和逐列递推的状态表⽰有较⼤的区别, ⽐如本⽂后⾯会讲到的Rocket Mania⼀题【确⽴状态】先提出⼀个⾮常重要的概念——“插头”.对于⼀个4连通的问题来说,它通常有上下左右4个插头,⼀个⽅向的插头存在表⽰这个格⼦在这个⽅向可以与外⾯相连.本题要求回路的个数,观察可以发现所有的⾮障碍格⼦⼀定是从⼀个格⼦进来,另⼀个格⼦出去,即4个插头恰好有2个插头存在,共6种情况.逐⾏递推不妨按照从上到下的顺序依次考虑每⼀⾏.分析第i ⾏的哪些信息对第i + 1⾏有影响:我们需要记录第i⾏的每个格⼦是否有下插头,这决定了第i+1⾏的每个格⼦是否有上插头.仅仅记录插头是否存在是不够的,可能导致出现多个回路 (如图),⽽本题要求⼀个回路,也就隐含着最后所有的⾮障碍格⼦通过插头连接成了⼀个连通块,因此还需要记录第i⾏的n个格⼦的连通情况.我们称图中的蓝线为轮廓线,任何时候只有轮廓线上⽅与其直接相连的格⼦和插头才会对轮廓线以下的格⼦产⽣直接的影响.通过上⾯的分析,可以写出动态规划的状态:表⽰前i⾏,第i⾏的n个格⼦是否具有下插头的⼀个n位的⼆进制数为,第i⾏的n个格⼦之间的连通性为的⽅案总数.如何表⽰n个格⼦的连通性呢? 通常给每⼀个格⼦标记⼀个正数,属于同⼀个的连通块的格⼦标记相同的数.⽐如{1,1,2,2}和{2,2,1,1}都表⽰第1,2个格⼦属于⼀个连通块,第3,4个格⼦属于⼀个连通块.为了避免出现同⼀个连通信息有不同的表⽰,⼀般会使⽤最⼩表⽰法.⼀种最⼩表⽰法为:所有的障碍格⼦标记为0,第⼀个⾮障碍格⼦以及与它连通的所有格⼦标记为1,然后再找第⼀个未标记的⾮障碍格⼦以及与它连通的格⼦标记为2,……,重复这个过程,直到所有的格⼦都标记完毕.⽐如连通信息((1,2,5),(3,6),(4))表⽰为{1,1,2,3,1,2}.还有⼀种最⼩表⽰法,即⼀个连通块内所有的格⼦都标记成该连通块最左边格⼦的列编号,⽐如上⾯这个例⼦,我们表⽰为{1,1,3,4,1,3}.两种表⽰⽅法在转移的时候略有不同,本⽂后⾯将会提到.如上图三个状态我们可以依次表⽰为,,.状态表⽰的优化通过观察可以发现如果轮廓线上⽅的n个格⼦中某个格⼦没有下插头,那么它就不会再与轮廓线以下的格⼦直接相连,它的连通性对轮廓线以下的格⼦不会再有影响,也就成为了“冗余”信息.不妨将记录格⼦的连通性改成记录插头的连通性,如果这个插头存在,那么就标记这个插头对应的格⼦的连通标号,如果这个插头不存在,那么标记为0.这样状态就从精简为,上图三个状态表⽰为,,.优化后不仅状态表⽰更加简单,⽽且状态总数将会⼤⼤减少.因为第⼀种表⽰法更加直观, 本⽂如果不作特殊说明, 默认使⽤第⼀种最⼩表⽰法逐格递推按照从上到下,从左到右的顺序依次考虑每⼀格.分析转移完(i, j)这个格⼦后哪些信息对后⾯的决策有影响:同样我们可以刻画出轮廓线,即轮廓线上⽅是已决策格⼦,下⽅是未决策格⼦.由图可知与轮廓线直接相连的格⼦有n个,直接相连的插头有n+1个,包括n个格⼦的下插头以及(i, j)的右插头.为了保持轮廓线的“连贯性”,不妨从左到右依次给n个格⼦标号,n+1个插头标号.类似地,我们需要记录与轮廓线直接相连的n+1个插头是否存在以及n个格⼦的连通情况.通过上⾯的分析,很容易写出动态规划的状态:表⽰当前转移完(i, j)这个格⼦,n+1个插头是否存在表⽰成⼀个n+1位的⼆进制数S0,以及n个格⼦的连通性为S1的⽅案总数.逐⾏递推的时候我们提到了状态的优化,同样地,我们也可以把格⼦的连通性记录在插头上,新的状态为,上图3个状态依次为,,.【转移状态】状态的转移开销主要包含两个⽅⾯:每个状态转移的状态数,计算新的状态的时间.逐⾏递推 假设从第i⾏转移到第i+1⾏,我们需要枚举第i+1⾏的每个格⼦的状态(共6种情况),对于任何⼀个⾮障碍格⼦,它是否有上插头和左插头已知,因此最多只有2种情况,状态的转移数≤2n.枚举完第i+1⾏每个格⼦的状态后,需要计算第i+1⾏n个格⼦之间的连通性的最⼩表⽰,通常可以使⽤并查集的Father数组对其重新标号或者重新执⾏⼀次BFS/DFS,时间复杂度为O(n),最后将格⼦的连通性转移到插头的连通性上.特别需要注意的是在转移的过程中,为了避免出现多个连通块,除了最后⼀⾏,任何时候⼀个连通分量内⾄少有⼀个格⼦有下插头.逐格递推 仔细观察下⾯这个图,当转移到时,轮廓线上n个格⼦只有(i-1, j)被改成(i, j),n+1个插头只有2个插头被改动,即(i, j-1)的右插头修改成(i, j)的下插头和(i-1,j)的下插头修改成(i, j)的右插头.转移的时候枚举(i, j)的状态分情况讨论.⼀般棋盘模型的逐格递推转移有3类情况:新建⼀个连通分量,合并两个连通分量,以及保持原来的连通分量.下⾯针对本题进⾏分析:情况1 新建⼀个连通分量,这种情况出现在(i, j)有右插头和下插头.新建的两个插头连通且不与其它插头连通,这种情况下需要将这两个插头连通分量标号标记成⼀个未标记过的正数,重新O(n)扫描保证新的状态满⾜最⼩表⽰.情况2 合并两个连通分量,这种情况出现在(i, j)有上插头和左插头.如果两个插头不连通,那么将两个插头所处的连通分量合并,标记相同的连通块标号,O(n)扫描保证最⼩表⽰;如果已经连通,相当于出现了⼀个回路,这种情况只能出现在最后⼀个⾮障碍格⼦.情况3 保持原来的连通分量,这种情况出现在(i, j)的上插头和左插头恰好有⼀个,下插头和右插头也恰好有⼀个.下插头或右插头相当于是左插头或上插头的延续,连通块标号相同,并且不会影响到其他的插头的连通块标号,计算新的状态的时间为O(1).注意当从⼀⾏的最后⼀个格⼦转移到下⼀⾏的第⼀个格⼦的时候,轮廓线需要特殊处理.值得⼀提的是,上⾯三种情况计算新的状态的时间分别为O(n), O(n), O(1),如果使⽤前⾯提到的第⼆种最⼩表⽰⽅法,情况1只需要O(1),但是情况3可能需要O(n)重新扫描.⽐较⼀下逐⾏递推和逐格递推的状态的转移,逐⾏递推的每⼀个转移的状态总数为指数级,⽽逐格递推为O(1),每次计算新的状态的时间两者最坏情况都为O(n),但是逐⾏递推的常数要⽐逐格递推⼤,从转移开销这个⾓度来看,逐格递推的优势是⽏庸置疑的.【程序实现】逐⾏递推和逐格递推的程序实现基本⼀致,下⾯以逐格递推为例来说明.⾸先必须解决的⼀个问题是,对于像这样的⼀个状态我们该如何存储,可以开⼀个长度为n+1的数组来存取n+1个插头的连通性,但是数组判重并不⽅便,⽽且空间较⼤.不妨将n+1个元素进⾏编码,⽤⼀个或⼏个整数来存储,当我们需要取⼀个状态出来对它进⾏修改的时候再进⾏解码.编码最简单的⽅法就是表⽰成⼀个n+1位的p进制数,p可以取能够达到的最⼤的连通块标号加1,对本题来说,最多出现个连通块,不妨取p = 7.在不会超过数据类型的范围的前提下,建议将p改成2的幂,因为位运算⽐普通的运算要快很多,本题最好采⽤8进制来存储.如需⼤范围修改连通块标号,最好将状态O(n) 解码到⼀个数组中,修改后再O(n)计算出新的p进制数,⽽对于只需要局部修改⼏个标号的情况下,可以直接⽤(x div p i-1) mod p来获取第i位的状态,⽤直接对第i位进⾏修改.最后我们探讨⼀下实现的⽅法,⼀般有两种⽅法:1.对所有可能出现的状态进⾏编码,枚举编码⽅式:预处理将所有可能的连通性状态搜索出来,依次编号1, 2, 3, …,Tot,那么状态为表⽰转移完(i, j)后轮廓线状态编号为k的⽅案总数.将所有状态存⼊Hash表中,使得每个状态与编号⼀⼀对应,程序框架如下:1 For i ←1 to m2 For j ←1 to n3 For k ←1 to Tot4 For x ← (i, j, State[k]) 的所有转移后的状态5←状态x的编号6,为的后继格⼦.7 End For因为还要把0留出来存没有插头的情况2.记忆化宽度优先搜索:将初始状态放⼊队列中,每次取队⾸元素进⾏扩展,并⽤Hash对扩展出来的新的状态判重.程序框架如下:1Queue.Push(所有初始状态)2While not Empty(Queue)3 p ← Queue.Pop()4 For x ← p的所有转移后的状态5If x之前扩展过 Then6 Sum [x] ← Sum[x] + Sum[p]7Else8Queue.Push(x)9Sum[x] ← Sum[p]10 End If11 End For12 End While⽐较上述两种实现⽅法,直接编码的⽅法实现简单,结构清晰,但是有⼀个很⼤的缺点:⽆效状态可能很多,导致了很多次空循环,⽽⼤⼤影响了程序的效率.下⾯是⼀组实验的⽐较数据:表1.直接编码与宽度优先搜索扩展状态总数⽐较可以看出直接编码扩展的⽆效状态的⽐率⾮常⾼,对于障碍较多的棋盘其对⽐更加明显,因此通常来说宽度优先搜索扩展⽐直接编码实现效率要⾼.Hash判重的优化:使⽤⼀个HashSize较⼩的Hash表,每转移⼀个(i, j)清空⼀次,每次判断状态x是否扩展过的程序效率⽐⽤⼀个HashSize较⼤的Hash表每次判断状态(i, j, x)⾼很多.类似地,在不需要记录路径的情况下,也可以使⽤滚动的扩展队列来代替⼀个⼤的扩展队列.最后我们⽐较⼀下,不同的实现⽅法对程序效率的影响:Program 1 :8-Based,枚举编码⽅式.Program 2 :8-Based,队列扩展,HashSize = 3999997.Program 3 :8-Based,队列扩展,HashSize = 4001,Hash表每次清空.Program 4 :7-Based,队列扩展,HashSize = 4001,Hash表每次清空.表2.不同的实现⽅法的程序效率的⽐较测试环境: Intel Core2 Duo T7100, 1.8GHz, 1G内存⼩结本章从划分阶段,确⽴状态,状态转移以及程序实现四个⽅⾯介绍了基于连通性状态压缩动态规划问题的⼀般解法,并在每个⽅⾯归纳了⼀些不同的⽅法,最后对不同的算法的效率进⾏⽐较.在平时的解题过程中我们要学会针对题⽬的特点和数据规模“对症下药”,选择最合适的⽅法⽽达到最好的效果.由于逐格递推的转移开销⽐逐⾏递推⼩很多,下⽂如果不作特殊说明,我们都采⽤逐格的阶段划分.⼆. ⼀类简单路径问题这⼀章我们会针对⼀类基于棋盘模型的简单回路和简单路径问题的解法作⼀个探讨.简单路径,即除了起点和终点可能相同外,其余顶点均不相同的路径,⽽简单回路为起点和终点相同的简单路径.Formula 1是⼀个典型的棋盘模型的简单回路问题,这⼀章我们继续以这个题为例来说明.⾸先我们分析⼀下简单回路问题有什么特点:仔细观察上⾯的图,可以发现轮廓线上⽅是由若⼲条互不相交的路径构成的,⽽每条路径的两个端⼝恰好对应了轮廓线上的两个插头! ⼀条路径上的所有格⼦对应的是⼀个连通块,⽽每条路径的两个端⼝对应的两个插头是连通的⽽且不与其他任何⼀个插头连通.在上⼀章我们提到了逐格递推转移的时候的三种情况:新建⼀个连通分量,合并两个连通分量,保持原来的连通分量,它们分别等价于两个插头成为了⼀条新的路径的两端,两条路径的两个端⼝连接起来形成⼀条更长的路径或⼀条路径的两个端⼝连接起来形成⼀个回路以及延长原来的路径.通过上⾯的分析我们知道了简单回路问题⼀定满⾜任何时候轮廓线上每⼀个连通分量恰好有2个插头,那么这些插头之间有什么性质呢? 【性质】轮廓线上从左到右4个插头a, b, c, d,如果a, c连通,并且与b不连通,那么b, d⼀定不连通.证明:反证法,如果a, c连通,b, d连通,那么轮廓线上⽅⼀定⾄少存在⼀条a到c的路径和⼀条b到d的路径.如图,两条路径⼀定会有交点,不妨设两条路径相交于格⼦P,那么P既与a, c连通,⼜与b, d连通,可以推出a, c与b, d连通,⽭盾,得证.这个性质对所有的棋盘模型的问题都适⽤.“两两匹配”,“不会交叉”这样的性质,我们很容易联想到括号匹配.将轮廓线上每⼀个连通分量中左边那个插头标记为左括号,右边那个插头标记为右括号,由于插头之间不会交叉,那么左括号⼀定可以与右括号⼀⼀对应.这样我们就可以使⽤3进制——0表⽰⽆插头,1表⽰左括号插头,2表⽰右括号插头记录下所有的轮廓线信息.不妨⽤#表⽰⽆插头,那么上⾯的三幅图分别对应的是(())#(),(()#)(),(()###),即,我们称这种状态的表⽰⽅法为括号表⽰法.依然分三类情况来讨论状态的转移:为了叙述⽅便,不妨称(i,j-1)的右插头为p,(i-1, j)的下插头为q,(i, j)的下插头为p',右插头为q',那么每次转移相当于轮廓线上插头p的信息修改成的信息p',插头q的信息修改成的信息q',设W(x) = 0, 1, 2表⽰插头x的状态.情况1 新建⼀个连通分量,这种情况下W(p) = 0,W(q) = 0,p',q'两个插头构建了⼀条新的路径,p'相当于为左括号,q'为右括号,即W(p')← 1,W(q')← 2,计算新的状态的时间为O(1).情况2 合并两个连通分量,这种情况下W(p) > 0,W(q) > 0,W(p')← 0,W(q')← 0,根据p, q为左括号还是右括号分四类情况讨论:情况2.1 W(p) = 1,W(q) = 1.那么需要将q这个左括号与之对应的右括号v修改成左括号,即W(v) ← 1.情况2.2 W(p) = 2,W(q) = 2.那么需要将p这个右括号与之对应的左括号v修改成右括号,即W(v)← 2.情况2.3 W(p) = 1,W(q) = 2,那么p和q是相对应的左括号和右括号,连接p, q相当于将⼀条路径的两端连接起来形成⼀个回路,这种情况下只能出现在最后⼀个⾮障碍格⼦. 情况2.4 W(p) = 2,W(q) = 1,那么p和q连接起来后,p对应的左括号和q对应的右括号恰好匹配,不需要修改其他的插头的状态.情况2.1, 2.2需要计算某个左括号或右括号与之匹配的括号,这个时候需要对三进制状态解码,利⽤类似模拟栈的⽅法.因此情况2.1, 2.2计算新的状态的时间复杂度为O(n),2.3, 2.4时间复杂度为O(1).情况3 保持原来的连通分量,W(p),W(q)中恰好⼀个为0,p',q'中也恰好⼀个为0.那么⽆论p',q'中哪个插头存在,都相当于是p, q中那个存在的插头的延续,括号性质⼀样,因此W(p')←W(p) + W(q),W(q')← 0或者W(q')←W(p) + W(q),W(p')← 0.计算新的状态的时间复杂度为O(1).通过上⾯的分析可以看出,括号表⽰法利⽤了简单回路问题的“⼀个连通分量内只有2个插头”的特殊性质巧妙地⽤3进制状态存储下完整的连通信息,插头的连通性标号相对独⽴,不再需要通过O(n)扫描⼤范围修改连通性标号.实现的时候,我们可以⽤4进制代替3进制⽽提⾼程序运算效率,下⾯对最⼩表⽰法与括号表⽰法的程序效率进⾏⽐较:表3.不同的状态表⽰的程序效率的⽐较可以看出,括号表⽰法的优势⾮常明显,加上它的思路清晰⾃然,实现也更加简单,因此对于解决这样⼀类简单回路问题是⾮常有价值的.类似的问题还有:NWERC 2004 Pipes,Hnoi2004 Postman,Hnoi2007 Park,还有⼀类⾮回路问题也可以通过棋盘改造后⽤简单回路问题的⽅法解决,⽐如 POJ 1739 Tony’s Tour:给⼀个m * n棋盘,有的格⼦是障碍,要求从左下⾓⾛到右下⾓,每个格⼦恰好经过⼀次,问⽅案总数.(m, n ≤ 8)只需要将棋盘改造⼀下,问题就等价于Formula 1了........#.. 改造成 .#####.... .##..#........介绍完简单回路问题的解法,那么⼀般的简单路径问题⼜如何解决呢?【例2】Formula 2问题描述给你⼀个m * n的棋盘,有的格⼦是障碍,要求从⼀个⾮障碍格⼦出发经过每个⾮障碍格⼦恰好⼀次,问⽅案总数.m, n ≤ 10.改编⾃Formula 1如图,⼀个2 * 2的⽆障碍棋盘,共有4条满⾜要求的路径.算法分析确⽴状态:按照从上到下,从左到右依次考虑每⼀个格⼦,设表⽰转移完(i, j)这个格⼦,轮廓线状态为S的⽅案总数.如果⽤⼀般的最⼩表⽰法,不仅需要记录每个插头的连通情况,还需要额外记录每个插头是否连接了路径的⼀端,状态表⽰相当复杂.依然从括号表⽰法这个⾓度来思考如何来存储轮廓线的状态:这个问题跟简单回路问题最⼤的区别为:不是所有的插头都两两匹配,有的插头连接的路径的另⼀端不是⼀个插头⽽是整条路径的⼀端,我们称这样的插头为独⽴插头.不妨将原来的3进制状态修改成4进制——0表⽰⽆插头,1表⽰左括号插头,2表⽰右括号插头,3表⽰独⽴插头,这样我们就可以⽤4进制完整地记录下轮廓线的信息,图中状态表⽰为(1203)4.状态转移:依然设(i, j-1)的右插头为p,(i-1, j)的下插头为q,(i, j)的下插头为p',右插头为q'.部分转移同简单回路问题完全⼀样,这⾥不再赘述,下⾯分三类情况讨论与独⽴插头有关的转移:情况1 W(p) = 0,W(q) = 0.当前格⼦可能成为路径的⼀端,即右插头或下插头是独⽴插头,因此W(p')←3,W(q')← 0或者W(q')← 3,W(p')← 0.情况2 W(p) > 0,W(q) > 0,那么W(p')← 0,W(q')← 0 情况2.1 W(p) =3,W(q) = 3,将插头p和q连接起来就相当于形成了⼀条完整的路径,这种情况只能出现在最后⼀个⾮障碍格⼦. 情况2.2 W(p) ,W(q) 中有⼀个为3,如果p为独⽴插头,那么⽆论q是左括号插头还是右括号插头,与q相匹配的插头v成为了独⽴插头,因此,W(v)←3.如果q为独⽴插头,类似处理.情况3 W(p) ,W(q) 中有⼀个>0,即p, q中有⼀个插头存在. 情况3.1 如果这个插头为独⽴插头,若在最后⼀个⾮障碍格⼦,这个插头可以成为路径的⼀端,否则可以⽤右插头或下插头来延续这个独⽴插头. 情况3.2 如果这个插头是左括号或右括号,那么我们以将这个插头“封住”,使它成为路径的⼀端,需要将这个插头所匹配的另⼀个插头的状态修改成为独⽴插头.情况2.2, 3.2需要计算某个左括号或右括号与之匹配的括号,计算新的状态的时间复杂度为O(n),其余情况计算新的状态的时间复杂度为O(1).特别需要注意,任何时候轮廓线上独⽴插头的个数不可以超过2个.⾄此问题完整解决,m = n = 10的⽆障碍棋盘,扩展的状态总数为3493315,完全可以承受.上⾯两类题⽬我们⽤括号表⽰法取得了很不错的效果,但是它存在⼀定的局限性,即插头必须满⾜两两匹配.那么对于更加⼀般的问题,⼀个连通分量内出现⼤于2个插头,上述的括号表⽰⽅法显得束⼿⽆策.下⾯将介绍⼀种括号表⽰法的变形,它可以适⽤于出现连通块内⼤于2个插头的问题,我们称之为⼴义的括号表⽰法:假设⼀个连通分量从左到右有多个插头,不妨将最左边的插头标记为“(”,最右边的插头标记为“)”,中间的插头全部标记为“)(”,那么能够匹配的括号对应的插头连通.如果问题中可能出现⼀个连通分量只有⼀个插头,那么这个插头标记为“( )”,这样插头之间的连通性可⽤括号序列完整地记录下来,⽐如对于⼀个连通性状态为{1,2,2,3,4,3,2,1},我们可以⽤(-(-)(-(-()-)-)-)记录.这种⼴义的括号表⽰⽅法需要⽤4进制甚⾄5进制存储状态,⽽且直接对状态连通性进⾏修改情况⾮常多,最好还是将状态进⾏解码,修改后再重新编码.下⽂我们将会运⽤⼴义的括号表⽰法解决⼀些具体的问题.⼩结本章针对⼀类简单路径问题,提出了⼀种新的状态表⽰⽅法——括号表⽰法,最后提出了⼴义的括号表⽰⽅法.相⽐普通的最⼩表⽰法,括号表⽰法巧妙地把连通块与括号匹配⼀⼀对应,使得状态更加简单明了,虽然不会减少扩展的状态总数,但是转移开销的常数要⼩很多,是⼀个不错的⽅法.三. ⼀类棋盘染⾊问题有⼀类这样的问题——给你⼀个m * n的棋盘,要求给每个格⼦染上⼀种颜⾊(共k种颜⾊),每种颜⾊的格⼦相互连通 (4连通).本章主要对这类问题的解法进⾏探讨,我们从⼀个例题说起:【例3】Black & White问题描述⼀个m * n的棋盘,有的格⼦已经染上⿊⾊或⽩⾊,现在要求将所有的未染⾊格⼦染上⿊⾊或⽩⾊,使得满⾜以下2个限制:1) 所有的⿊⾊的格⼦是连通的,所有的⽩⾊格⼦也是连通的.2) 不会有⼀个2 * 2的⼦矩阵的4个格⼦的颜⾊全部相同.问⽅案总数.(m, n ≤ 8)如下图,m = 2,n = 3,灰⾊格⼦为未染⾊格⼦,共有9种染⾊⽅案.Source : Uva10572算法分析这是⼀个典型的棋盘染⾊问题,着⾊规则有:1) 只有⿊⽩两种颜⾊,即k = 2,并且同⾊的格⼦互相连通.2) 没有同⾊的2 * 2的格⼦.对于简单路径问题来说,相邻的格⼦是否连通取决于它们之间的插头是否存在,状态记录轮廓线上每个插头是否存在以及插头之间的连通性;⽽棋盘染⾊问题相邻的格⼦是否连通取决于它们的颜⾊是否相同,这就需要记录轮廓线上⽅n个格⼦的颜⾊以及格⼦之间的连通性.确⽴状态 设当前转移完Q(i, j)这个格⼦,对以后的决策产⽣影响的信息有:轮廓线上⽅n个格⼦的染⾊情况以及它们的连通性,由第2条着⾊规则“没有同⾊的2 * 2的格⼦”可知P(i-1, j)的颜⾊会影响到(i, j+1)着⾊,因此我们还需要额外记录格⼦的颜⾊.动态规划的状态为:表⽰转移完(i, j),轮廓线上从左到右n个格⼦的染⾊情况为S0 (0 ≤ S0 < 2n),连通性状态为S1,格⼦的颜⾊为cp(0或1)的⽅案总数.状态的精简 如果相邻的2个格⼦不属于同⼀个连通块,那么它们必然不同⾊,因此只需要记录(i, 1)和(i-1, j+1)两个格⼦的颜⾊,利⽤S1就可以推出n个格⼦的颜⾊.这个精简不会减少状态的总数,仍然需要⼀个变量来记录两个格⼦的颜⾊,因此意义并不⼤,这⾥只是提⼀下.。

dp有哪些种类

dp有哪些种类

dp有哪些种类dp有哪些种类⼀、总结⼀句话总结:⼆、dp动态规划分类详解动态规划⼀直是ACM竞赛中的重点,同时⼜是难点,因为该时间效率⾼,代码量少,多元性强,主要考察思维能⼒、建模抽象能⼒、灵活度。

******************************************************************************************动态规划(:Dynamic programming,DP)是⼀种在、和中使⽤的,通过把原问题分解为相对简单的⼦问题的⽅式求解复杂问题的⽅法。

动态规划常常适⽤于有和性质的问题,动态规划⽅法所耗时间往往远少于朴素解法。

动态规划背后的基本思想⾮常简单。

⼤致上,若要解⼀个给定问题,我们需要解其不同部分(即⼦问题),再合并⼦问题的解以得出原问题的解。

通常许多⼦问题⾮常相似,为此动态规划法试图仅仅解决每个⼦问题⼀次,从⽽减少计算量:⼀旦某个给定⼦问题的解已经算出,则将其存储,以便下次需要同⼀个⼦问题解之时直接查表。

这种做法在重复⼦问题的数⽬关于输⼊的规模呈时特别有⽤。

动态规划问题满⾜三⼤重要性质最优⼦结构性质:如果问题的最优解所包含的⼦问题的解也是最优的,我们就称该问题具有最优⼦结构性质(即满⾜最优化原理)。

最优⼦结构性质为动态规划算法解决问题提供了重要线索。

⼦问题重叠性质:⼦问题重叠性质是指在⽤递归算法⾃顶向下对问题进⾏求解时,每次产⽣的⼦问题并不总是新问题,有些⼦问题会被重复计算多次。

动态规划算法正是利⽤了这种⼦问题的重叠性质,对每⼀个⼦问题只计算⼀次,然后将其计算结果保存在⼀个表格中,当再次需要计算已经计算过的⼦问题时,只是在表格中简单地查看⼀下结果,从⽽获得较⾼的效率。

:将各阶段按照⼀定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态⽆法直接影响它未来的决策,⽽只能通过当前的这个状态。

换句话说,每个状态都是过去历史的⼀个完整总结。

poj dp题目列表

poj dp题目列表

[1]POJ 动态规划题目列表容易:1018, 1050, 1083, 1088, 1125, 1143, 1157, 1163, 1178, 1179, 1189, 1208, 1276, 1322, 1414, 1456, 1458, 1609, 1644, 1664, 1690, 1699, 1740(博弈), 1742, 1887, 1926(马尔科夫矩阵,求平衡), 1936, 1952, 1953, 1958, 1959, 1962, 1975, 1989, 2018, 2029, 2039, 2063, 2081, 2082, 2181, 2184, 2192, 2231, 2279, 2329, 2336, 2346, 2353, 2355, 2356, 2385, 2392, 2424,不易:1019, 1037, 1080, 1112, 1141, 1170, 1192, 1239, 1655, 1695, 1707, 1733(区间减法加并查集), 1737, 1837, 1850, 1920(加强版汉罗塔), 1934(全部最长公共子序列), 1937(计算几何), 1964(最大矩形面积,O(n)算法), 2138, 2151, 2161, 2178,推荐:1015, 1635, 1636(挺好的), 1671, 1682, 1692(优化), 1704, 1717, 1722, 1726, 1732, 1770, 1821, 1853, 1949, 2019, 2127, 2176, 2228, 2287, 2342, 2374, 2378, 2384, 24111015 Jury Compromise1029 False coin1036 Gangsters1037 A decorative fence1038 Bugs Integrated, Inc.1042 Gone Fishing1050 To the Max1062 昂贵的聘礼1074 Parallel Expectations1080 Human Gene Functions1088 滑雪1093 Formatting Text1112 Team Them Up!1141 Brackets Sequence1143 Number Game1157 LITTLE SHOP OF FLOWERS1159 Palindrome1160 Post Office1163 The Triangle1170 Shopping Offers1178 Camelot1179 Polygon1180 Batch Scheduling1185 炮兵阵地1187 陨石的秘密1189 钉子和小球1191 棋盘分割1192 最优连通子集1208 The Blocks Problem1239 Increasing Sequences1240 Pre-Post-erous!1276 Cash Machine1293 Duty Free Shop1322 Chocolate1323 Game Prediction1338 Ugly Numbers1390 Blocks1414 Life Line1432 Decoding Morse Sequences 1456 Supermarket1458 Common Subsequence1475 Pushing Boxes1485 Fast Food1505 Copying Books1513 Scheduling Lectures1579 Function Run Fun1609 Tiling Up Blocks1631 Bridging signals 2分+DP NLOGN 1633 Gladiators1635 Subway tree systems1636 Prison rearrangement1644 To Bet or Not To Bet1649 Market Place1651 Multiplication Puzzle1655 Balancing Act1661 Help Jimmy1664 放苹果1671 Rhyme Schemes1682 Clans on the Three Gorges 1690 (Your)((Term)((Project)))1691 Painting A Board1692 Crossed Matchings1695 Magazine Delivery1699 Best Sequence1704 Georgia and Bob1707 Sum of powers1712 Flying Stars1714 The Cave1717 Dominoes1718 River Crossing1722 SUBTRACT1726 Tango Tango Insurrection 1732 Phone numbers1733 Parity game1737 Connected Graph1740 A New Stone Game1742 Coins P1745 Divisibility1770 Special Experiment1771 Elevator Stopping Plan 1776 Task Sequences1821 Fence1837 Balance1848 Tree1850 Code1853 Cat1874 Trade on Verweggistan 1887 Testing the CATCHER 1889 Package Pricing1920 Towers of Hanoi1926 Pollution1934 Trip1936 All in All1937 Balanced Food1946 Cow Cycling1947 Rebuilding Roads1949 Chores1952 BUY LOW, BUY LOWER 1953 World Cup Noise1958 Strange Towers of Hanoi 1959 Darts1962 Corporative Network 1964 City Game1975 Median Weight Bead 1989 The Cow Lineup2018 Best Cow Fences2019 Cornfields2029 Get Many Persimmon Trees 2033 Alphacode2039 To and Fro2047 Concert Hall Scheduling 2063 Investment2081 Recaman's Sequence 2082 Terrible Sets2084 Game of Connections2127 Greatest Common Increasing Subsequence 2138 Travel Games2151 Check the difficulty of problems2152 Fire2161 Chandelier2176 Folding2178 Heroes Of Might And Magic2181 Jumping Cows2184 Cow Exhibition2192 Zipper2193 Lenny's Lucky Lotto Lists2228 Naptime2231 Moo Volume2279 Mr. Young's Picture Permutations2287 TianJi -- The Horse Racing2288 Islands and Bridges2292 Optimal Keypad2329 Nearest number - 22336 Ferry Loading II2342 Anniversary party2346 Lucky tickets2353 Ministry2355 Railway tickets2356 Find a multiple2374 Fence Obstacle Course2378 Tree Cutting2384 Harder Sokoban Problem2385 Apple Catching2386 Lake Counting2392 Space Elevator2397 Spiderman2411 Mondriaan's Dream2414 Phylogenetic Trees Inherited2424 Flo's Restaurant2430 Lazy Cows2915 Zuma3017 Cut the Sequence3028 Shoot-out3124 The Bookcase3133 Manhattan Wiring3345 Bribing FIPA3375 Network Connection3420 Quad Tiling ?/?cat=5[2]动态规划方法总结1. 按状态类型分写在前面:从状态类型分,并不表示一题只从属于一类。

气体压缩机原理

气体压缩机原理

气体压缩机原理
气体压缩机是一种将气体压缩并提高压力的机器。

它可以将气体压缩到高压状态,以便用于各种工业和商业应用中,如气体输送、燃烧、冷却、干燥等。

气体压缩机原理是基于泵的原理,但其压缩的介质是气体而不是液体。

气体压缩机的工作原理是将气体从低压状态压缩到高压状态。

它的工作过程可以分为四个阶段:吸气、压缩、排气和冷却。

其中,吸气是指将气体从外部吸入机器中,压缩是指将气体压缩为高压状态,排气则是将高压气体排出机器,冷却则是通过冷却装置将气体冷却至合适的温度。

气体压缩机可以分为往复式压缩机和旋转式压缩机两种类型。

往复式压缩机是一种通过往复运动将气体压缩的机器。

它通常由一对活塞和气缸组成,通过连杆和曲轴转动将活塞往复运动,从而将气体压缩。

旋转式压缩机则是一种通过旋转将气体压缩的机器。

它通常由一对旋转器和压缩腔组成,通过旋转将气体压缩。

气体压缩机在工业和商业应用中有着广泛的用途。

例如,在石油和天然气工业中,气体压缩机被用于将天然气压缩成高压气体,以便输送和储存。

在制冷和空调行业中,气体压缩机被用于将制冷剂压缩为高压气体,以便进行冷却。

在食品和饮料工业中,气体压缩机被用于将空气压缩成高压气体,以便用于灌装和包装。

气体压缩机是一种重要的机器,其原理基于泵的原理,但其压缩的介质是气体而不是液体。

气体压缩机可以分为往复式压缩机和旋转式压缩机两种类型,并广泛应用于工业和商业领域中。

Poj动态规划

Poj动态规划

[1]POJ 动态规划题目列表容易:1018, 1050, 1083, 1088, 1125, 1143, 1157, 1163, 1178, 1179, 1189, 1208, 1276, 1322, 1414, 1456, 1458, 1609, 1644, 1664, 1690, 1699, 1740(博弈), 1742, 1887, 1926(马尔科夫矩阵,求平衡), 1936,1952, 1953, 1958, 1959, 1962, 1975, 1989, 2018, 2029,2039, 2063, 2081, 2082,2181, 2184, 2192, 2231, 2279, 2329, 2336, 2346, 2353,2355, 2356, 2385, 2392, 2424,不易:1019,1037, 1080, 1112, 1141, 1170, 1192, 1239, 1655, 1695, 1707,1733(区间减法加并查集), 1737, 1837, 1850, 1920(加强版汉罗塔), 1934(全部最长公共子序列), 1937(计算几何), 1964(最大矩形面积,O(n)算法), 2138, 2151, 2161(烦,没写), 2178,推荐:1015, 1635, 1636(挺好的), 1671, 1682, 1692(优化), 1704, 1717, 1722, 1726, 1732, 1770, 1821, 1853, 1949, 2019, 2127, 2176, 2228, 2287, 2342, 2374, 2378, 2384, 2411状态DP树DP构造最优解四边形不等式单调队列1015 Jury Compromise1029 False coin1036 Gangsters1037 A decorative fence1038 Bugs Integrated, Inc.1042 Gone Fishing1050 To the Max1062 昂贵的聘礼1074 Parallel Expectations1080 Human Gene Functions1088 滑雪1093 Formatting Text1112 Team Them Up!1141 Brackets Sequence1143 Number Game1157 LITTLE SHOP OF FLOWERS1159 Palindrome1160 Post Office1163 The Triangle1170 Shopping Offers1178 Camelot1179 Polygon1180 Batch Scheduling1185 炮兵阵地1187 陨石的秘密1189 钉子和小球1191 棋盘分割1192 最优连通子集1208 The Blocks Problem1239 Increasing Sequences1240 Pre-Post-erous!1276 Cash Machine1293 Duty Free Shop1322 Chocolate1323 Game Prediction1338 Ugly Numbers1390 Blocks1414 Life Line1432 Decoding Morse Sequences 1456 Supermarket1458 Common Subsequence1475 Pushing Boxes1485 Fast Food1505 Copying Books1513 Scheduling Lectures1579 Function Run Fun1609 Tiling Up Blocks1631 Bridging signals 2分+DP NLOGN 1633 Gladiators1635 Subway tree systems1636 Prison rearrangement1644 To Bet or Not To Bet1649 Market Place1651 Multiplication Puzzle1655 Balancing Act1661 Help Jimmy1664 放苹果1671 Rhyme Schemes1682 Clans on the Three Gorges 1690 (Your)((Term)((Project)))1691 Painting A Board1692 Crossed Matchings 1695 Magazine Delivery 1699 Best Sequence1704 Georgia and Bob1707 Sum of powers1712 Flying Stars1714 The Cave1717 Dominoes1718 River Crossing1722 SUBTRACT1726 Tango Tango Insurrection 1732 Phone numbers1733 Parity game1737 Connected Graph1740 A New Stone Game 1742 Coins P1745 Divisibility1770 Special Experiment 1771 Elevator Stopping Plan 1776 Task Sequences1821 Fence1837 Balance1848 Tree1850 Code1853 Cat1874 Trade on Verweggistan 1887 Testing the CATCHER 1889 Package Pricing1920 Towers of Hanoi1926 Pollution1934 Trip1936 All in All1937 Balanced Food1946 Cow Cycling1947 Rebuilding Roads1949 Chores1952 BUY LOW, BUY LOWER 1953 World Cup Noise1958 Strange Towers of Hanoi 1959 Darts1962 Corporative Network 1964 City Game1975 Median Weight Bead 1989 The Cow Lineup2018 Best Cow Fences2019 Cornfields2029 Get Many Persimmon Trees2033 Alphacode2039 To and Fro2047 Concert Hall Scheduling2063 Investment2081 Recaman's Sequence2082 Terrible Sets2084 Game of Connections2127 Greatest Common Increasing Subsequence 2138 Travel Games2151 Check the difficulty of problems2152 Fire2161 Chandelier2176 Folding2178 Heroes Of Might And Magic2181 Jumping Cows2184 Cow Exhibition2192 Zipper2193 Lenny's Lucky Lotto Lists2228 Naptime2231 Moo Volume2279 Mr. Young's Picture Permutations2287 TianJi -- The Horse Racing2288 Islands and Bridges2292 Optimal Keypad2329 Nearest number - 22336 Ferry Loading II2342 Anniversary party2346 Lucky tickets2353 Ministry2355 Railway tickets2356 Find a multiple2374 Fence Obstacle Course2378 Tree Cutting2384 Harder Sokoban Problem2385 Apple Catching2386 Lake Counting2392 Space Elevator2397 Spiderman2411 Mondriaan's Dream2414 Phylogenetic Trees Inherited2424 Flo's Restaurant2430 Lazy Cows2915 Zuma3017 Cut the Sequence3028 Shoot-out3124 The Bookcase3133 Manhattan Wiring3345 Bribing FIPA3375 Network Connection3420 Quad Tiling ?/?cat=5[2]动态规划方法总结1. 按状态类型分写在前面:从状态类型分,并不表示一题只从属于一类。

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题长沙市雅礼中学陈丹琦【摘要】基于状态压缩的动态规划问题是一类以集合信息为状态且状态总数为指数级的特殊的动态规划问题.在状态压缩的基础上,有一类问题的状态中必须要记录若干个元素的连通情况,我们称这样的问题为基于连通性状态压缩的动态规划问题,本文着重对这类问题的解法及优化进行探讨和研究.本文主要从动态规划的几个步骤——划分阶段,确立状态,状态转移以及程序实现来介绍这类问题的一般解法,会特别针对到目前为止信息学竞赛中涌现出来的几类题型的解法作一个探讨.结合例题,本文还会介绍作者在减少状态总数和降低转移开销两个方面对这类问题优化的一些心得.【关键词】状态压缩连通性括号表示法轮廓线插头棋盘模型【目录】【序言】 (3)【正文】 (5)一. 问题的一般解法 (5)【例1】Formula 1 (5)问题描述 (5)算法分析 (5)小结 (11)二. 一类简单路径问题 (12)【例2】Formula 2 (15)问题描述 (15)算法分析 (15)小结 (16)三. 一类棋盘染色问题 (17)【例3】Black & White (17)问题描述 (17)算法分析 (17)小结 (19)四. 一类基于非棋盘模型的问题 (20)【例4】生成树计数 (20)问题描述 (20)算法分析 (20)小结 (21)五. 一类最优性问题的剪枝技巧 (22)【例5】Rocket Mania (22)问题描述 (22)算法分析 (23)小结 (25)六.总结 (25)【参考文献】 (26)【感谢】 (26)【附录】 (26)【序言】先看一个非常经典的问题——旅行商问题(即TSP 问题,Traveling Salesman Problem):一个n(≤15)个点的带权完全图,求权和最小的经过每个点恰好一次的封闭回路.这个问题已经被证明是NP 完全问题,那么对于这样一类无多项式算法的问题,搜索算法是不是解决问题的唯一途径呢? 答案是否定的.不难发现任何时候我们只需要知道哪些点已经被遍历过而遍历点的具体顺序对以后的决策是没有影响的,因此不妨以当前所在的位置i ,遍历过的点的集合S 为状态作动态规划:(,)min{(,{})(,)}f i S f j S i dist j i =-+,其中j<>i ,i ,j in S .动态规划的时间复杂度为2(2*)n O n ,虽然为指数级算法,但是对于n = 15的数据规模来说已经比朴素的(!)O n 的搜索算法高效很多了.我们通常把这样一类以一个集合内的元素信息作为状态且状态总数为指数级别的动态规划称为基于状态压缩的动态规划或集合动态规划.基于状态压缩的动态规划问题通常具有以下两个特点:1.数据规模的某一维或几维非常小;2.它需要具备动态规划问题的两个基本性质:最优性原理和无后效性.一般的状态压缩问题,压缩的是一个小范围内每个元素的决策,状态中元素的信息相对独立.而有些问题,仅仅记录每个元素的决策是不够的,不妨再看一个例子:给你一个m * n (m, n ≤9) 的矩阵,每个格子有一个价值,i j V ,要求找一个连通块使得该连通块内所有格子的价值之和最大.按从上到下的顺序依次考虑每个格子选还是不选,下图为一个极端情况,其中黑色的格子为所选的连通块.只考虑前5行的时候,所有的黑色格子形成了三个连通块,而最后所有的黑色格子形成一个连通块.如果状态中只单纯地记录前一行或前几行的格子选还是不选,是无法准确描述这个状态的,因此压缩的状态中我们需要增加一维,记录若干个格子之间的连通情况.我们把这一类必须要在状态中记录若干个元素之间的连通信息的问题称为基于连通性状态压缩的动态规划问题.本文着重对这类问题进行研究.连通是图论中一个非常重要的概念,在一个无向图中,如果两个顶点之间存在一条路径,则称这两个点连通.而基于连通性状态压缩的动态规划问题与图论模型有着密切的关联,比如后文涉及到的哈密尔顿回路、生成树等等.通常这类问题的本身与连通性有关或者隐藏着连通信息.全文共有六个章节.第一章,问题的一般解法,介绍解决基于连通性状态压缩的动态规划问题的一般思路和解题技巧;第二章,一类简单路径问题,介绍一类基于棋盘模型的简单路径问题的状态表示的改进——括号表示法以及提出广义的括号表示法;第三章,一类棋盘染色问题,介绍解决一类棋盘染色问题的一般思路;第四章,一类基于非棋盘模型的问题,介绍解决一类非棋盘模型的连通性状态压缩问题的一般思路;第五章,一类最优性问题的剪枝技巧,本章的重点是优化,探讨如何通过剪枝来减少扩展的状态的总数从而提高算法的效率;第六章,总结,回顾前文,总结解题方法.【正文】一. 问题的一般解法基于连通性状态压缩的动态规划问题通常具有一个比较固定的模式,几乎所有的题目都是在这个模式的基础上变形和扩展的.本章选取了一个有代表性的例题来介绍这一类问题的一般解法.【例1】Formula 11问题描述给你一个m * n的棋盘,有的格子是障碍,问共有多少条回路使得经过每个非障碍格子恰好一次.m, n ≤ 12.如图,m = n = 4,(1, 1), (1, 2)是障碍,共有2条满足要求的回路.算法分析【划分阶段】这是一个典型的基于棋盘模型的问题,棋盘模型的特殊结构,使得它成为连通性状态压缩动态规划问题最常见的“舞台”.通常来说,棋盘模型有三种划分阶段的方法:逐行,逐列,逐格.顾名思义,逐行即从上到下或从下到上依次考虑每一行的状态,并转移到下一行;逐列即从左到右或从右到左依次考虑每一列的状态,并转移到下一列;逐格即按一定的顺序(如从上到下,从左到右)依次考虑每一格的状态,并转移到下一个格子.对于本题来说,逐行递推和逐列递推基本类似2,接下来我们会对逐行递推和逐格递推的状态确立,状态转移以及程序实现一一介绍.1Ural1519, Timus Top Coders : Third Challenge2有的题目, 逐行递推和逐列递推的状态表示有较大的区别, 比如本文后面会讲到的Rocket Mania一题【确立状态】 先提出一个非常重要的概念——“插头”.对于一个4连通的问题来说,它通常有上下左右4个插头,一个方向的插头存在表示这个格子在这个方向可以与外面相连.本题要求回路的个数,观察可以发现所有的非障碍格子一定是从一个格子进来,另一个格子出去,即4个插头恰好有2个插头存在,共6种情况.逐行递推 不妨按照从上到下的顺序依次考虑每一行.分析第i 行的哪些信息对第i + 1行有影响:我们需要记录第i 行的每个格子是否有下插头,这决定了第i+1行的每个格子是否有上插头.仅仅记录插头是否存在是不够的,可能导致出现多个回路 (如右图),而本题要求一个回路,也就隐含着最后所有的非障碍格子通过插头连接成了一个连通块,因此还需要记录第i 行的n 个格子的连通情况.插头:0011 插头:1111 插头:10013连通性:(3,4) 连通性:(1,2) (3,4) 连通性:(1,2,3,4)4我们称图中的蓝线为轮廓线,任何时候只有轮廓线上方与其直接相连的格子和插头才会对轮廓线以下的格子产生直接的影响.通过上面的分析,可以写出动态规划的状态:01(,,)f i S S 表示前i 行,第i 行的n 个格子是否具有下插头的一个n 位的二进制数为0S ,第i 行的n 个格子之间的连通性为1S 的方案总数.如何表示n 个格子的连通性呢? 通常给每一个格子标记一个正数,属于同一个的连通块的格子标记相同的数.比如{1,1,2,2}和{2,2,1,1}都表示第1,2个格子属于一个连通块,第3,4个格子属于一个连通块.为了避免出现同一个连通信息有不同的表示,一般会使用最小表示法.一种最小表示法为:所有的障碍格子标记为0,第一个非障碍格子以及与它连通的所有格子标记为1,然后再找第一个未标记的非障碍格子以及与它连通的格子标记为2,……,重复这个过程,直到所有的格子都标记完毕.比如连通信息((1,2,5),(3,6),(4))表示为{1,1,2,3,1,2}.还有一种最小表示法,即一个连通块内所有的格子都标记成该连通块最左边格子的列编号,比如上面这个例子,我们表3从左到右, 0表示无插头, 1表示有插头 4 括号内的数表示的是格子的列编号, 一个括号内的格子属于一个连通块示为{1,1,3,4,1,3}.两种表示方法在转移的时候略有不同,本文后面将会提到5.如上图三个状态我们可以依次表示为2(1,(0011),{0,0,1,1})f ,2(2,(1111),{1,1,2,2})f ,2(3,(1001),{1,1,1,1})f .状态表示的优化 通过观察可以发现如果轮廓线上方的n 个格子中某个格子没有下插头,那么它就不会再与轮廓线以下的格子直接相连,它的连通性对轮廓线以下的格子不会再有影响,也就成为了“冗余”信息.不妨将记录格子的连通性改成记录插头的连通性,如果这个插头存在,那么就标记这个插头对应的格子的连通标号,如果这个插头不存在,那么标记为0.这样状态就从01(,,)f i S S 精简为(,)f i S ,上图三个状态表示为(1,{0,0,1,1})f ,(2,{1,1,2,2})f ,(3,{1,0,0,1})f .优化后不仅状态表示更加简单,而且状态总数将会大大减少.逐格递推 按照从上到下,从左到右的顺序依次考虑每一格.分析转移完(i, j)这个格子后哪些信息对后面的决策有影响:同样我们可以刻画出轮廓线,即轮廓线上方是已决策格子,下方是未决策格子.由图可知与轮廓线直接相连的格子有n 个,直接相连的插头有n+1个,包括n个格子的下插头以及(i, j)的右插头.为了保持轮廓线的“连贯性”,不妨从左到右依次给n 个格子标号,n+1个插头标号.类似地,我们需要记录与轮廓线直接相连的n+1个插头是否存在以及n 个格子的连通情况.通过上面的分析,很容易写出动态规划的状态:01(,,,)f i j S S 表示当前转移完(i, j)这个格子,n+1个插头是否存在表示成一个n+1位的二进制数S 0,以及n 个格子的连通性为S 1的方案总数.2(3,1,(10111),{1,1,2,2})f 2(3,2,(10111),{1,1,2,2})f 2(3,3,(10001),{1,1,1,1})f 逐行递推的时候我们提到了状态的优化,同样地,我们也可以把格子的连通性记录在插头上,新的状态为(,,)f i j S ,上图3个状态依次为(3,1,{1,0,1,2,2})f ,(3,2,{1,0,1,2,2})f ,(3,3,{1,0,0,0,1})f .5因为第一种表示法更加直观, 本文如果不作特殊说明, 默认使用第一种最小表示法【转移状态】状态的转移开销主要包含两个方面:每个状态转移的状态数,计算新的状态的时间.逐行递推 假设从第i 行转移到第i+1行,我们需要枚举第i+1行的每个格子的状态(共6种情况),对于任何一个非障碍格子,它是否有上插头和左插头已知,因此最多只有2种情况,状态的转移数≤2n .枚举完第i+1行每个格子的状态后,需要计算第i+1行n 个格子之间的连通性的最小表示,通常可以使用并查集的Father 数组对其重新标号或者重新执行一次BFS/DFS ,时间复杂度为O(n),最后将格子的连通性转移到插头的连通性上.特别需要注意的是在转移的过程中,为了避免出现多个连通块,除了最后一行,任何时候一个连通分量内至少有一个格子有下插头.逐格递推 仔细观察下面这个图,当(,1,)f i j S 转移到(,,')f i j S 时,轮廓线上n 个格子只有(i-1, j)被改成(i, j),n+1个插头只有2个插头被改动,即(i, j-1)的右插头修改成(i, j)的下插头和(i-1,j)的下插头修改成(i, j)的右插头.转移的时候枚举(i, j)的状态分情况讨论.一般棋盘模型的逐格递推转移有3类情况:新建一个连通分量,合并两个连通分量,以及保持原来的连通分量.下面针对本题进行分析:情况1 新建一个连通分量,这种情况出现在(i, j)有右插头和下插头.新建Condition I Condition IIICondition II的两个插头连通且不与其它插头连通,这种情况下需要将这两个插头连通分量标号标记成一个未标记过的正数,重新O(n)扫描保证新的状态满足最小表示.情况2 合并两个连通分量,这种情况出现在(i, j)有上插头和左插头.如果两个插头不连通,那么将两个插头所处的连通分量合并,标记相同的连通块标号,O(n)扫描保证最小表示;如果已经连通,相当于出现了一个回路,这种情况只能出现在最后一个非障碍格子.情况3 保持原来的连通分量,这种情况出现在(i, j)的上插头和左插头恰好有一个,下插头和右插头也恰好有一个.下插头或右插头相当于是左插头或上插头的延续,连通块标号相同,并且不会影响到其他的插头的连通块标号,计算新的状态的时间为O(1).注意当从一行的最后一个格子转移到下一行的第一个格子的时候,轮廓线需要特殊处理.值得一提的是,上面三种情况计算新的状态的时间分别为O(n), O(n), O(1),如果使用前面提到的第二种最小表示方法,情况1只需要O(1),但是情况3可能需要O(n)重新扫描.比较一下逐行递推和逐格递推的状态的转移,逐行递推的每一个转移的状态总数为指数级,而逐格递推为O(1),每次计算新的状态的时间两者最坏情况都为O(n),但是逐行递推的常数要比逐格递推大,从转移开销这个角度来看,逐格递推的优势是毋庸置疑的.【程序实现】逐行递推和逐格递推的程序实现基本一致,下面以逐格递推为例来说明.首先必须解决的一个问题是,对于像(3,2,{1,0,1,2,2})f 这样的一个状态我们该如何存储,可以开一个长度为n+1的数组来存取n+1个插头的连通性,但是数组判重并不方便,而且空间较大.不妨将n+1个元素进行编码,用一个或几个整数来存储,当我们需要取一个状态出来对它进行修改的时候再进行解码.编码最简单的方法就是表示成一个n+1位的p 进制数,p 可以取能够达到的最大的连通块标号加16,对本题来说,最多出现/26n ≤⎢⎥⎣⎦个连通块,不妨取p =7.在不会超过数据类型的范围的前提下,建议将p 改成2的幂,因为位运算比普通的运算要快很多,本题最好采用8进制来存储.如需大范围修改连通块标号,最好将状态O(n) 解码到一个数组中,修改后再O(n)计算出新的p 进制数,而对于只需要局部修改几个标号的情况下,可以直接用(x div p i-1) mod p 来获取第i 位的状态,用1*i k p -±直接对第i 位进行修改.最后我们探讨一下实现的方法,一般有两种方法:6 因为还要把0留出来存没有插头的情况1.对所有可能出现的状态进行编码,枚举编码方式:预处理将所有可能的 连通性状态搜索出来,依次编号1, 2, 3, …,Tot ,那么状态为(,,)f i j k 表示转移完(i, j)后轮廓线状态编号为k 的方案总数.将所有状态存入Hash 表中,使得每个状态与编号一一对应,程序框架如下:2.记忆化宽度优先搜索:将初始状态放入队列中,每次取队首元素进行扩展,并用Hash 对扩展出来的新的状态判重.程序框架如下:比较上述两种实现方法,直接编码的方法实现简单,结构清晰,但是有一个很大的缺点:无效状态可能很多,导致了很多次空循环,而大大影响了程序的效率.下面是一组实验的比较数据:表1.直接编码与宽度优先搜索扩展状态总数比较可以看出直接编码扩展的无效状态的比率非常高,对于障碍较多的棋盘其对比更加明显,因此通常来说宽度优先搜索扩展比直接编码实现效率要高.Hash判重的优化:使用一个HashSize较小的Hash表,每转移一个(i, j)清空一次,每次判断状态x是否扩展过的程序效率比用一个HashSize较大的Hash 表每次判断状态(i, j, x)高很多.类似地,在不需要记录路径的情况下,也可以使用滚动的扩展队列来代替一个大的扩展队列.最后我们比较一下,不同的实现方法对程序效率的影响7:Program 1 :8-Based,枚举编码方式.Program 2 :8-Based,队列扩展,HashSize = 3999997.Program 3 :8-Based,队列扩展,HashSize = 4001,Hash表每次清空.Program 4 :7-Based,队列扩展,HashSize = 4001,Hash表每次清空.表2.不同的实现方法的程序效率的比较小结本章从划分阶段,确立状态,状态转移以及程序实现四个方面介绍了基于连通性状态压缩动态规划问题的一般解法,并在每个方面归纳了一些不同的方法,最后对不同的算法的效率进行比较.在平时的解题过程中我们要学会针对题目的特点和数据规模“对症下药”,选择最合适的方法而达到最好的效果.由于逐格递推的转移开销比逐行递推小很多,下文如果不作特殊说明,我们都采用逐格的阶段划分.7测试环境: Intel Core2 Duo T7100, 1.8GHz, 1G内存二. 一类简单路径问题这一章我们会针对一类基于棋盘模型的简单回路和简单路径问题的解法作一个探讨.简单路径,即除了起点和终点可能相同外,其余顶点均不相同的路径,而简单回路为起点和终点相同的简单路径.Formula 1是一个典型的棋盘模型的简单回路问题,这一章我们继续以这个题为例来说明.首先我们分析一下简单回路问题有什么特点:仔细观察上面的图,可以发现轮廓线上方是由若干条互不相交的路径构成的,而每条路径的两个端口恰好对应了轮廓线上的两个插头! 一条路径上的所有格子对应的是一个连通块,而每条路径的两个端口对应的两个插头是连通的而且不与其他任何一个插头连通.在上一章我们提到了逐格递推转移的时候的三种情况:新建一个连通分量,合并两个连通分量,保持原来的连通分量,它们分别等价于两个插头成为了一条新的路径的两端,两条路径的两个端口连接起来形成一条更长的路径或一条路径的两个端口连接起来形成一个回路以及延长原来的路径.通过上面的分析我们知道了简单回路问题一定满足任何时候轮廓线上每一个连通分量恰好有2个插头,那么这些插头之间有什么性质呢?【性质】轮廓线上从左到右4个插头a, b, c, d,如果a, c连通,并且与b不连通,那么b, d一定不连通.证明:反证法,如果a, c连通,b, d连通,那么轮廓线上方一定至少存在一条a到c的路径和一条b到d的路径.如图,两条路径一定会有交点,不妨设两条路径相交于格子P,那么P既与a, c连通,又与b, d连通, dca可以推出a, c 与b, d 连通,矛盾,得证.这个性质对所有的棋盘模型的问题都适用.“两两匹配”,“不会交叉”这样的性质,我们很容易联想到括号匹配.将轮廓线上每一个连通分量中左边那个插头标记为左括号,右边那个插头标记为右括号,由于插头之间不会交叉,那么左括号一定可以与右括号一一对应.这样我们就可以使用3进制——0表示无插头,1表示左括号插头,2表示右括号插头记录下所有的轮廓线信息.不妨用#表示无插头,那么上面的三幅图分别对应的是(())#(),(()#)(),(()###),即333(1122012),(1120212),(1120002),我们称这种状态的表示方法为括号表示法.依然分三类情况来讨论状态的转移:为了叙述方便,不妨称(i,j-1)的右插头为p ,(i-1, j)的下插头为q ,(i, j)的下插头为p ',右插头为q ',那么每次转移相当于轮廓线上插头p 的信息修改成p '的信息,插头q 的信息修改成q '的信息,设W(x) = 0, 1, 2表示插头x 的状态.情况1 新建一个连通分量,这种情况下W(p) = 0,W(q) = 0,p ',q '两个插头构建了一条新的路径,相当于p '为左括号,q '为右括号,即()W p '← 1,()W q '← 2,计算新的状态的时间为O(1).情况2 合并两个连通分量,这种情况下W(p) > 0,W(q) > 0,()W p '← 0,()W q '← 0,根据p, q 为左括号还是右括号分四类情况讨论:情况2.1 W(p) = 1,W(q) = 1.那么需要将q 这个左括号与之对应的右括号v 修改成左括号,即W(v) ← 1.情况2.2 W(p) = 2,W(q) = 2.那么需要将p 这个右括号与之对应的左括号v 修改成右括号,即W(v)← 2.情况2.3 W(p) = 1,W(q) = 2,那么p 和q 是相对应的左括号和右括号,连接p, q 相当于将一条路径的两端连接起来形成一个回路,这种情况下只能出现在最后一个非障碍格子.情况2.4 W(p) = 2,W(q) = 1,那么p 和q 连接起来后,p 对应的左括号和q 对应的右括号恰好匹配,不需要修改其他的插头的状态.情况2.1图 v ) 情况2.2图情况2.1, 2.2需要计算某个左括号或右括号与之匹配的括号,这个时候需要对三进制状态解码,利用类似模拟栈的方法.因此情况2.1, 2.2计算新的状态的时间复杂度为O(n),2.3, 2.4时间复杂度为O(1).情况3 保持原来的连通分量,W(p),W(q)中恰好一个为0,()W p ',()W q '中也恰好一个为0.那么无论p ',q '中哪个插头存在,都相当于是p, q 中那个存在的插头的延续,括号性质一样,因此()W p '← W(p) + W(q),()W q '← 0或者()W q '← W(p) + W(q),()W p '← 0.计算新的状态的时间复杂度为O(1).通过上面的分析可以看出,括号表示法利用了简单回路问题的“一个连通分量内只有2个插头”的特殊性质巧妙地用3进制状态存储下完整的连通信息,插头的连通性标号相对独立,不再需要通过O(n)扫描大范围修改连通性标号.实现的时候,我们可以用4进制代替3进制而提高程序运算效率,下面对最小表示法与括号表示法的程序效率进行比较:表3.不同的状态表示的程序效率的比较可以看出,括号表示法的优势非常明显,加上它的思路清晰自然,实现也更加简单,因此对于解决这样一类简单回路问题是非常有价值的.类似的问题还有:NWERC 2004 Pipes ,Hnoi2004 Postman ,Hnoi2007 Park ,还有一类非回路问题也可以通过棋盘改造后用简单回路问题的方法解决,比如 POJ 1739 Tony ’s Tour :给一个m * n 棋盘,有的格子是障碍,要求从左下角走到右下角,每个格子恰好经过一次,问方案总数.(m, n ≤ 8)只需要将棋盘改造一下,问题就等价于Formula 1了........#.. 改造成 .#####.情况2.4图... .##..#........介绍完简单回路问题的解法,那么一般的简单路径问题又如何解决呢?【例2】Formula 28问题描述给你一个m * n的棋盘,有的格子是障碍,要求从一个非障碍格子出发经过每个非障碍格子恰好一次,问方案总数.m, n ≤ 10.如图,一个2 * 2的无障碍棋盘,共有4条满足要求的路径.算法分析确立状态:按照从上到下,从左到右依次考虑每一个格子,设(,,)f i j S表示转移完(i, j)这个格子,轮廓线状态为S的方案总数.如果用一般的最小表示法,不仅需要记录每个插头的连通情况,还需要额外记录每个插头是否连接了路径的一端,状态表示相当复杂.依然从括号表示法这个角度来思考如何来存储轮廓线的状态:这个问题跟简单回路问题最大的区别为:不是所有的插头都两两匹配,有的插头连接的路径的另一端不是一个插头而是整条路径的一端,我们称这样的插头为独立插头.不妨将原来的3进制状态修改成4进制——0表示无插头,1表示左括号插头,2表示右括号插头,3表示独立插头,这样我们就可以用4进制完整地记录下轮廓线的信息,图中状态表示为(1203)4.状态转移:依然设(i, j-1)的右插头为p,(i-1, j)的下插头为q,(i, j)的下插头为p',右插头为q'.部分转移同简单回路问题完全一样,这里不再赘述,下面分三类情况讨论与独立插头有关的转移:情况1 W(p) = 0,W(q) = 0.当前格子可能成为路径的一端,即右插头或8改编自Formula 1。

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题

基于连通性状态压缩的动态规划问题长沙市雅礼中学陈丹琦【摘要】基于状态压缩的动态规划问题是一类以集合信息为状态且状态总数为指数级的特殊的动态规划问题.在状态压缩的基础上,有一类问题的状态中必须要记录若干个元素的连通情况,我们称这样的问题为基于连通性状态压缩的动态规划问题,本文着重对这类问题的解法及优化进行探讨和研究.本文主要从动态规划的几个步骤——划分阶段,确立状态,状态转移以及程序实现来介绍这类问题的一般解法,会特别针对到目前为止信息学竞赛中涌现出来的几类题型的解法作一个探讨.结合例题,本文还会介绍作者在减少状态总数和降低转移开销两个方面对这类问题优化的一些心得.【关键词】状态压缩连通性括号表示法轮廓线插头棋盘模型【目录】【序言】 (3)【正文】 (5)一. 问题的一般解法 (5)【例1】Formula 1 (5)问题描述 (5)算法分析 (5)小结 (11)二. 一类简单路径问题 (12)【例2】Formula 2 (15)问题描述 (15)算法分析 (15)小结 (16)三. 一类棋盘染色问题 (17)【例3】Black & White (17)问题描述 (17)算法分析 (17)小结 (19)四. 一类基于非棋盘模型的问题 (20)【例4】生成树计数 (20)问题描述 (20)算法分析 (20)小结 (21)五. 一类最优性问题的剪枝技巧 (22)【例5】Rocket Mania (22)问题描述 (22)算法分析 (22)小结 (25)六.总结 (25)【参考文献】 (26)【感谢】 (26)【附录】 (26)【序言】先看一个非常经典的问题——旅行商问题(即TSP 问题,Traveling Salesman Problem):一个n (≤15)个点的带权完全图,求权和最小的经过每个点恰好一次的封闭回路.这个问题已经被证明是NP 完全问题,那么对于这样一类无多项式算法的问题,搜索算法是不是解决问题的唯一途径呢? 答案是否定的.不难发现任何时候我们只需要知道哪些点已经被遍历过而遍历点的具体顺序对以后的决策是没有影响的,因此不妨以当前所在的位置i ,遍历过的点的集合S 为状态作动态规划:(,)m i n {(,{})(f i S f j S i d i s t j i =-+,其中j<>i ,i ,j in S .动态规划的时间复杂度为2(2*)n O n ,虽然为指数级算法,但是对于n = 15的数据规模来说已经比朴素的(!)O n 的搜索算法高效很多了.我们通常把这样一类以一个集合内的元素信息作为状态且状态总数为指数级别的动态规划称为基于状态压缩的动态规划或集合动态规划.基于状态压缩的动态规划问题通常具有以下两个特点:1.数据规模的某一维或几维非常小;2.它需要具备动态规划问题的两个基本性质:最优性原理和无后效性.一般的状态压缩问题,压缩的是一个小范围内每个元素的决策,状态中元素的信息相对独立.而有些问题,仅仅记录每个元素的决策是不够的,不妨再看一个例子:给你一个m * n (m , n ≤9) 的矩阵,每个格子有一个价值,i j V ,要求找一个连通块使得该连通块内所有格子的价值之和最大.按从上到下的顺序依次考虑每个格子选还是不选,下图为一个极端情况,其中黑色的格子为所选的连通块.只考虑前5行的时候,所有的黑色格子形成了三个连通块,而最后所有的黑色格子形成一个连通块.如果状态中只单纯地记录前一行或前几行的格子选还是不选,是无法准确描述这个状态的,因此压缩的状态中我们需要增加一维,记录若干个格子之间的连通情况.我们把这一类必须要在状态中记录若干个元素之间的连通信息的问题称为基于连通性状态压缩的动态规划问题.本文着重对这类问题进行研究.连通是图论中一个非常重要的概念,在一个无向图中,如果两个顶点之间存在一条路径,则称这两个点连通.而基于连通性状态压缩的动态规划问题与图论模型有着密切的关联,比如后文涉及到的哈密尔顿回路、生成树等等.通常这类问题的本身与连通性有关或者隐藏着连通信息.全文共有六个章节.第一章,问题的一般解法,介绍解决基于连通性状态压缩的动态规划问题的一般思路和解题技巧;第二章,一类简单路径问题,介绍一类基于棋盘模型的简单路径问题的状态表示的改进——括号表示法以及提出广义的括号表示法;第三章,一类棋盘染色问题,介绍解决一类棋盘染色问题的一般思路;第四章,一类基于非棋盘模型的问题,介绍解决一类非棋盘模型的连通性状态压缩问题的一般思路;第五章,一类最优性问题的剪枝技巧,本章的重点是优化,探讨如何通过剪枝来减少扩展的状态的总数从而提高算法的效率;第六章,总结,回顾前文,总结解题方法.【正文】一. 问题的一般解法基于连通性状态压缩的动态规划问题通常具有一个比较固定的模式,几乎所有的题目都是在这个模式的基础上变形和扩展的.本章选取了一个有代表性的例题来介绍这一类问题的一般解法.【例1】Formula 11问题描述给你一个m * n的棋盘,有的格子是障碍,问共有多少条回路使得经过每个非障碍格子恰好一次.m, n≤ 12.如图,m = n = 4,(1, 1), (1, 2)是障碍,共有2条满足要求的回路.算法分析【划分阶段】这是一个典型的基于棋盘模型的问题,棋盘模型的特殊结构,使得它成为连通性状态压缩动态规划问题最常见的“舞台”.通常来说,棋盘模型有三种划分阶段的方法:逐行,逐列,逐格.顾名思义,逐行即从上到下或从下到上依次考虑每一行的状态,并转移到下一行;逐列即从左到右或从右到左依次考虑每一列的状态,并转移到下一列;逐格即按一定的顺序(如从上到下,从左到右)依次考虑每一格的状态,并转移到下一个格子.对于本题来说,逐行递推和逐列递推基本类似2,接下来我们会对逐行递推和逐格递推的状态确立,状态转移以及程序实现一一介绍.1Ural1519, Timus Top Coders : Third Challenge2有的题目, 逐行递推和逐列递推的状态表示有较大的区别, 比如本文后面会讲到的Rocket Mania一题【确立状态】 先提出一个非常重要的概念——“插头”.对于一个4连通的问题来说,它通常有上下左右4个插头,一个方向的插头存在表示这个格子在这个方向可以与外面相连.本题要求回路的个数,观察可以发现所有的非障碍格子一定是从一个格子进来,另一个格子出去,即4个插头恰好有2个插头存在,共6种情况.逐行递推 不妨按照从上到下的顺序依次考虑每一行.分析第i 行的哪些信息对第i + 1行有影响:我们需要记录第i 行的每个格子是否有下插头,这决定了第i +1行的每个格子是否有上插头.仅仅记录插头是否存在是不够的,可能导致出现多个回路 (如右图),而本题要求一个回路,也就隐含着最后所有的非障碍格子通过插头连接成了一个连通块,因此还需要记录第i 行的n 个格子的连通情况.插头:0011 插头:1111 插头:10013连通性:(3,4) 连通性:(1,2) (3,4) 连通性:(1,2,3,4)4我们称图中的蓝线为轮廓线,任何时候只有轮廓线上方与其直接相连的格子和插头才会对轮廓线以下的格子产生直接的影响.通过上面的分析,可以写出动态规划的状态:01(,,)f i S S 表示前i 行,第i 行的n 个格子是否具有下插头的一个n 位的二进制数为0S ,第i 行的n 个格子之间的连通性为1S 的方案总数.如何表示n 个格子的连通性呢? 通常给每一个格子标记一个正数,属于同一个的连通块的格子标记相同的数.比如{1,1,2,2}和{2,2,1,1}都表示第1,2个格子属于一个连通块,第3,4个格子属于一个连通块.为了避免出现同一个连通信息有不同的表示,一般会使用最小表示法.一种最小表示法为:所有的障碍格子标记为0,第一个非障碍格子以及与它连通的所有格子标记为1,然后再找第一个未标记的非障碍格子以及与它连通的格子标记为2,……,重复这个过程,直到所有的格子都标记完毕.比如连通信息((1,2,5),(3,6),(4))表示为{1,1,2,3,1,2}.还有一种最小表示法,即一个连通块内所有的格子都标记成该连通块最左边格子的列编号,比如上面这个例子,我们表3从左到右, 0表示无插头, 1表示有插头 4 括号内的数表示的是格子的列编号, 一个括号内的格子属于一个连通块示为{1,1,3,4,1,3}.两种表示方法在转移的时候略有不同,本文后面将会提到5.如上图三个状态我们可以依次表示为2(1,(0011),{0,0,1,1})f ,2(2,(1111),{1,1,2,2})f ,2(3,(1001),{1,1,1,1})f .状态表示的优化 通过观察可以发现如果轮廓线上方的n 个格子中某个格子没有下插头,那么它就不会再与轮廓线以下的格子直接相连,它的连通性对轮廓线以下的格子不会再有影响,也就成为了“冗余”信息.不妨将记录格子的连通性改成记录插头的连通性,如果这个插头存在,那么就标记这个插头对应的格子的连通标号,如果这个插头不存在,那么标记为0.这样状态就从01(,,)f i S S 精简为(,)f i S ,上图三个状态表示为(1,{0,0,1,1})f ,(2,{1,1,2,2})f ,(3,{1,0,0,1})f . 优化后不仅状态表示更加简单,而且状态总数将会大大减少.逐格递推 按照从上到下,从左到右的顺序依次考虑每一格.分析转移完(i , j )这个格子后哪些信息对后面的决策有影响:同样我们可以刻画出轮廓线,即轮廓线上方是已决策格子,下方是未决策格子.由图可知与轮廓线直接相连的格子有n个,直接相连的插头有n +1个,包括n 个格子的下插头以及(i , j )的右插头.为了保持轮廓线的“连贯性”,不妨从左到右依次给n 个格子标号,n +1个插头标号.类似地,我们需要记录与轮廓线直接相连的n +1个插头是否存在以及n 个格子的连通情况.通过上面的分析,很容易写出动态规划的状态:01(,,,)f i j S S 表示当前转移完(i , j )这个格子,n +1个插头是否存在表示成一个n +1位的二进制数S 0,以及n 个格子的连通性为S 1的方案总数.2(3,1,(10111),{1,1,2,2})f 2(3,2,(10111),{1,1,2,2})f 2(3,3,(10001),{1,1,1,1})f 逐行递推的时候我们提到了状态的优化,同样地,我们也可以把格子的连通性记录在插头上,新的状态为(,,)f i j S ,上图3个状态依次为(3,1,{1,0,1,2,2})f ,(3,2,{1,0,1,2,2})f ,(3,3,{1,0,0,0,1})f .5因为第一种表示法更加直观, 本文如果不作特殊说明, 默认使用第一种最小表示法【转移状态】状态的转移开销主要包含两个方面:每个状态转移的状态数,计算新的状态的时间.逐行递推 假设从第i 行转移到第i +1行,我们需要枚举第i +1行的每个格子的状态(共6种情况),对于任何一个非障碍格子,它是否有上插头和左插头已知,因此最多只有2种情况,状态的转移数≤2n .枚举完第i +1行每个格子的状态后,需要计算第i +1行n 个格子之间的连通性的最小表示,通常可以使用并查集的Father 数组对其重新标号或者重新执行一次BFS/DFS ,时间复杂度为O (n ),最后将格子的连通性转移到插头的连通性上.特别需要注意的是在转移的过程中,为了避免出现多个连通块,除了最后一行,任何时候一个连通分量内至少有一个格子有下插头.逐格递推 仔细观察下面这个图,当(,1,)f i j S 转移到(,,')f i j S 时,轮廓线上n 个格子只有(i -1, j)被改成(i , j ),n +1个插头只有2个插头被改动,即(i , j -1)的右插头修改成(i , j )的下插头和(i -1,j )的下插头修改成(i , j )的右插头.转移的时候枚举(i , j )的状态分情况讨论.一般棋盘模型的逐格递推转移有3类情况:新建一个连通分量,合并两个连通分量,以及保持原来的连通分量.下面针对本题进行分析:情况1 新建一个连通分量,这种情况出现在(i , j )有右插头和下插头.新建的两个插头连通且不与其它插头连通,这种情况下需要将这两个插头连通分量标号标记成一个未标记过的正数,重新O (n )扫描保证新的状态满足最小表示.Condition ICondition IIICondition II情况2 合并两个连通分量,这种情况出现在(i , j )有上插头和左插头.如果两个插头不连通,那么将两个插头所处的连通分量合并,标记相同的连通块标号,O (n )扫描保证最小表示;如果已经连通,相当于出现了一个回路,这种情况只能出现在最后一个非障碍格子.情况3 保持原来的连通分量,这种情况出现在(i , j )的上插头和左插头恰好有一个,下插头和右插头也恰好有一个.下插头或右插头相当于是左插头或上插头的延续,连通块标号相同,并且不会影响到其他的插头的连通块标号,计算新的状态的时间为O (1).注意当从一行的最后一个格子转移到下一行的第一个格子的时候,轮廓线需要特殊处理.值得一提的是,上面三种情况计算新的状态的时间分别为O (n ), O (n ), O (1),如果使用前面提到的第二种最小表示方法,情况1只需要O (1),但是情况3可能需要O (n )重新扫描.比较一下逐行递推和逐格递推的状态的转移,逐行递推的每一个转移的状态总数为指数级,而逐格递推为O (1),每次计算新的状态的时间两者最坏情况都为O (n ),但是逐行递推的常数要比逐格递推大,从转移开销这个角度来看,逐格递推的优势是毋庸置疑的.【程序实现】逐行递推和逐格递推的程序实现基本一致,下面以逐格递推为例来说明.首先必须解决的一个问题是,对于像(3,2,{1,0,1,2,2})f 这样的一个状态我们该如何存储,可以开一个长度为n +1的数组来存取n +1个插头的连通性,但是数组判重并不方便,而且空间较大.不妨将n +1个元素进行编码,用一个或几个整数来存储,当我们需要取一个状态出来对它进行修改的时候再进行解码.编码最简单的方法就是表示成一个n +1位的p 进制数,p 可以取能够达到的最大的连通块标号加16,对本题来说,最多出现/26n ≤⎢⎥⎣⎦个连通块,不妨取p =7.在不会超过数据类型的范围的前提下,建议将p 改成2的幂,因为位运算比普通的运算要快很多,本题最好采用8进制来存储.如需大范围修改连通块标号,最好将状态O (n ) 解码到一个数组中,修改后再O (n )计算出新的p 进制数,而对于只需要局部修改几个标号的情况下,可以直接用(x div p i -1) mod p 来获取第i 位的状态,用1*i k p -±直接对第i 位进行修改.最后我们探讨一下实现的方法,一般有两种方法:1.对所有可能出现的状态进行编码,枚举编码方式:预处理将所有可能的 连通性状态搜索出来,依次编号1, 2, 3, …,Tot ,那么状态为(,,)f i j k 表示转移完6 因为还要把0留出来存没有插头的情况(i , j )后轮廓线状态编号为k 的方案总数.将所有状态存入Hash 表中,使得每个状态与编号一一对应,程序框架如下:2.记忆化宽度优先搜索:将初始状态放入队列中,每次取队首元素进行扩展,并用Hash 对扩展出来的新的状态判重.程序框架如下:比较上述两种实现方法,直接编码的方法实现简单,结构清晰,但是有一个很大的缺点:无效状态可能很多,导致了很多次空循环,而大大影响了程序的效率.下面是一组实验的比较数据:表1.直接编码与宽度优先搜索扩展状态总数比较 测试数据 宽度优先搜索 扩展状态总数直接编码 Tot Tot * m * n 无效状态比率 m = 9, n = 9 (1,1)为障碍 309302188 177228 82.5% m = 10, n = 10 无障碍 1340115798 579800 76.8% m = 11, n = 11 (1,1)为障碍 33326415511 1876831 82.2% m = 12, n = 12无障碍 1333113 41835 6024240 77.9%For i ← 1 to mFor j ←1 to nFor k ← 1 to TotFor x ← (i , j , State [k ]) 的所有转移后的状态'k ← 状态x 的编号(,,)(,,)(,,)f i j k f i j k f i j k ''''''←+,(,)i j ''为(,)i j 的后继格子.End ForQueue.Push(所有初始状态) While not Empty(Queue) p ← Queue.Pop() For x ← p 的所有转移后的状态 If x 之前扩展过 Then Sum [x ] ← Sum[x ] + Sum[p ] Else Queue.Push(x ) Sum[x ] ← Sum[p ] End If End For End While可以看出直接编码扩展的无效状态的比率非常高,对于障碍较多的棋盘其对比更加明显,因此通常来说宽度优先搜索扩展比直接编码实现效率要高.Hash判重的优化:使用一个HashSize较小的Hash表,每转移一个(i, j)清空一次,每次判断状态x是否扩展过的程序效率比用一个HashSize较大的Hash表每次判断状态(i, j, x)高很多.类似地,在不需要记录路径的情况下,也可以使用滚动的扩展队列来代替一个大的扩展队列.最后我们比较一下,不同的实现方法对程序效率的影响7:Program 1 :8-Based,枚举编码方式.Program 2 :8-Based,队列扩展,HashSize = 3999997.Program 3 :8-Based,队列扩展,HashSize = 4001,Hash表每次清空.Program 4 :7-Based,队列扩展,HashSize = 4001,Hash表每次清空.表2.不同的实现方法的程序效率的比较测试数据Program 1 Program 2 Program 3 Program 4m = 10, n = 1046ms 31ms 15ms 31ms 无障碍棋盘m = 11, n = 11140ms 499ms 109ms 187ms (1,1)为障碍m = 12, n = 12624ms 1840ms 499ms 873ms 无障碍小结本章从划分阶段,确立状态,状态转移以及程序实现四个方面介绍了基于连通性状态压缩动态规划问题的一般解法,并在每个方面归纳了一些不同的方法,最后对不同的算法的效率进行比较.在平时的解题过程中我们要学会针对题目的特点和数据规模“对症下药”,选择最合适的方法而达到最好的效果.由于逐格递推的转移开销比逐行递推小很多,下文如果不作特殊说明,我们都采用逐格的阶段划分.7测试环境: Intel Core2 Duo T7100, 1.8GHz, 1G内存二. 一类简单路径问题这一章我们会针对一类基于棋盘模型的简单回路和简单路径问题的解法作一个探讨.简单路径,即除了起点和终点可能相同外,其余顶点均不相同的路径,而简单回路为起点和终点相同的简单路径.Formula 1是一个典型的棋盘模型的简单回路问题,这一章我们继续以这个题为例来说明.首先我们分析一下简单回路问题有什么特点:仔细观察上面的图,可以发现轮廓线上方是由若干条互不相交的路径构成的,而每条路径的两个端口恰好对应了轮廓线上的两个插头! 一条路径上的所有格子对应的是一个连通块,而每条路径的两个端口对应的两个插头是连通的而且不与其他任何一个插头连通.在上一章我们提到了逐格递推转移的时候的三种情况:新建一个连通分量,合并两个连通分量,保持原来的连通分量,它们分别等价于两个插头成为了一条新的路径的两端,两条路径的两个端口连接起来形成一条更长的路径或一条路径的两个端口连接起来形成一个回路以及延长原来的路径.通过上面的分析我们知道了简单回路问题一定满足任何时候轮廓线上每一个连通分量恰好有2个插头,那么这些插头之间有什么性质呢?【性质】轮廓线上从左到右4个插头a , b , c , d ,如果a , c 连通,并且与b 不连通,那么b , d 一定不连通.证明:反证法,如果a , c 连通,b , d 连通,那么轮廓线上方一定至少存在一条a 到c 的路径和一条b 到d的路径.如图,两条路径一定会有交点,不妨设两条路径相交于格子P ,那么P 既与a , c 连通,又与b , d 连通,可以推出a , c 与b , d 连通,矛盾,得证.这个性质对所有的棋盘模型的问题都适用.“两两匹配”,“不会交叉”这样的性质,我们很容易联想到括号匹配.将轮廓线上每一个连通分量中左边那个插头标记为左括号,右边那个插头标记为右括号,由于插头之间不会交叉,那么左括号一定可以与右括号一一对应.这样我们 dc a b就可以使用3进制——0表示无插头,1表示左括号插头,2表示右括号插头记录下所有的轮廓线信息.不妨用#表示无插头,那么上面的三幅图分别对应的是(())#(),(()#)(),(()###),即333(1122012),(1120212),(1120002),我们称这种状态的表示方法为括号表示法.依然分三类情况来讨论状态的转移:为了叙述方便,不妨称(i,j-1)的右插头为p,(i-1, j)的下插头为q,(i, j)的下插头为p',右插头为q',那么每次转移相当于轮廓线上插头p的信息修改成p'的信息,插头q的信息修改成q'的信息,设W(x) = 0, 1, 2表示插头x的状态.情况1 新建一个连通分量,这种情况下W(p) = 0,W(q) = 0,p',q'两个插头构建了一条新的路径,相当于p'为左括号,q'为右括号,即()W p'←1,()W q'← 2,计算新的状态的时间为O(1).情况2 合并两个连通分量,这种情况下W(p) > 0,W(q) > 0,()W p'← 0,()W q'← 0,根据p, q为左括号还是右括号分四类情况讨论:情况2.1W(p) = 1,W(q) = 1.那么需要将q这个左括号与之对应的右括号v修改成左括号,即W(v) ← 1.情况2.2W(p) = 2,W(q) = 2.那么需要将p这个右括号与之对应的左括号v修改成右括号,即W(v)← 2.情况2.3W(p) = 1,W(q) = 2,那么p和q是相对应的左括号和右括号,连接p, q相当于将一条路径的两端连接起来形成一个回路,这种情况下只能出现在最后一个非障碍格子.情况2.4W(p) = 2,W(q) = 1,那么p和q连接起来后,p对应的左括号和q对应的右括号恰好匹配,不需要修改其他的插头的状态.pq 情况2.3图pq()情况2.4图qp v( ) 情况2.1图qp (v)情况2.2图情况2.1, 2.2需要计算某个左括号或右括号与之匹配的括号,这个时候需要对三进制状态解码,利用类似模拟栈的方法.因此情况2.1, 2.2计算新的状态的时间复杂度为O(n),2.3, 2.4时间复杂度为O(1).情况3 保持原来的连通分量,W(p),W(q)中恰好一个为0,()W p',()W q'中也恰好一个为0.那么无论p',q'中哪个插头存在,都相当于是p, q中那个存在的插头的延续,括号性质一样,因此()W p'←W(p) + W(q),()W q'←0或者()W q'←W(p) + W(q),()W p'← 0.计算新的状态的时间复杂度为O(1).通过上面的分析可以看出,括号表示法利用了简单回路问题的“一个连通分量内只有2个插头”的特殊性质巧妙地用3进制状态存储下完整的连通信息,插头的连通性标号相对独立,不再需要通过O(n)扫描大范围修改连通性标号.实现的时候,我们可以用4进制代替3进制而提高程序运算效率,下面对最小表示法与括号表示法的程序效率进行比较:表3.不同的状态表示的程序效率的比较测试数据最小表示法7Based最小表示法8Based括号表示法3Based括号表示法4Basedm = 10, n = 10无障碍棋盘31ms 15ms 0ms 0ms m = 11, n = 11(1,1)为障碍187ms 109ms 46ms 31ms m = 12, n = 12无障碍873ms 499ms 265ms 140ms 可以看出,括号表示法的优势非常明显,加上它的思路清晰自然,实现也更加简单,因此对于解决这样一类简单回路问题是非常有价值的.类似的问题还有:NWERC 2004 Pipes,Hnoi2004 Postman,Hnoi2007 Park,还有一类非回路问题也可以通过棋盘改造后用简单回路问题的方法解决,比如POJ 1739 Tony’s Tour:给一个m * n棋盘,有的格子是障碍,要求从左下角走到右下角,每个格子恰好经过一次,问方案总数.(m, n ≤ 8)只需要将棋盘改造一下,问题就等价于Formula 1了........#.. 改造成 .#####.... .##..#........介绍完简单回路问题的解法,那么一般的简单路径问题又如何解决呢?【例2】Formula 28问题描述给你一个m * n的棋盘,有的格子是障碍,要求从一个非障碍格子出发经过每个非障碍格子恰好一次,问方案总数.m, n≤ 10.如图,一个2 * 2的无障碍棋盘,共有4条满足要求的路径.算法分析确立状态:按照从上到下,从左到右依次考虑每一个格子,设(,,)f i j S表示转移完(i, j)这个格子,轮廓线状态为S的方案总数.如果用一般的最小表示法,不仅需要记录每个插头的连通情况,还需要额外记录每个插头是否连接了路径的一端,状态表示相当复杂.依然从括号表示法这个角度来思考如何来存储轮廓线的状态:这个问题跟简单回路问题最大的区别为:不是所有的插头都两两匹配,有的插头连接的路径的另一端不是一个插头而是整条路径的一端,我们称这样的插头为独立插头.不妨将原来的3进制状态修改成4进制——0表示无插头,1表示左括号插头,2表示右括号插头,3表示独立插头,这样我们就可以用4进制完整地记录下轮廓线的信息,图中状态表示为(1203)4.状态转移:依然设(i, j-1)的右插头为p,(i-1, j)的下插头为q,(i, j)的下插头为p',右插头为q'.部分转移同简单回路问题完全一样,这里不再赘述,下面分三类情况讨论与独立插头有关的转移:情况1 W(p) = 0,W(q) = 0.当前格子可能成为路径的一端,即右插头或下插头是独立插头,因此()W p'← 0.W q'← 3,()W q'← 0或者()W p'←3,()情况2 W(p) > 0,W(q) > 0,那么()W p'← 0,()W q'← 0情况2.1 W(p) =3,W(q) = 3,将插头p和q连接起来就相当于形成了8改编自Formula 1。

状态压缩DP入门

状态压缩DP入门

THale Waihona Puke P最后的结果是: min( dp[( 1<<n ) – 1][j] ) ( 0 <= j < n );
技巧:利用2进制,使得一个整数表示一个点 集,这样集合的操作可以用位运算来实现。 例如从集合i中去掉点j: k = i & ( ~( 1 << j ) ) 或者 k = i - ( 1 << j )


遍历点集i中都包含哪些点 for(j=0;(1<<j)<=i;j++) {

if(((1<<j)&i)!=0)

点集i就包含点j

} 把点j加入点集i i=(i|(1<<j));

TSP
如何表示一个点集: 由于只有16个点,所以我们用一个整数表示 一个点集: 例如: 5 = 0000000000000101;(2进制表示) 它的第0位和第2位是1,就表示这个点集里有 2个点,分别是点0和点2。 31 = 0000000000011111; (2进制表示) 表示这个点集里有5个点,分别是0,1,2,4, 5;
经典入门
状态压缩动态规划
状态压缩动态规划

状态压缩动态规划: 动态规划的状态有时候比较恶心,不容 易表示出来,需要用一些编码技术,把 状态压缩的用简单的方式表示出来。
典型方式:当需要表示一个集合有哪些 元素时,往往利用2进制用一个整数表示。

经典问题:TSP



一个n个点的带权的有向图,求一条路径, 使得这条路经过每个点恰好一次,并且 路径上边的权值和最小(或者最大)。 或者求一条具有这样性质的回路,这是 经典的TSP问题。 n <= 16 (重要条件,状态压缩的标志) 今天讲第一个问题的状态压缩动态规划 的解法,第2个问题大同小异。

分组背包问题常见解法

分组背包问题常见解法

分组背包问题常见解法
常见的解法包括动态规划和状态压缩。

动态规划解法:
1. 定义`dp[i][j]`表示前i个物品选择j个分组所能获得的最大价值。

2. 初始化`dp`数组为0。

3. 设置状态转移方程为`dp[i][j] = max(dp[i-1][j], dp[i-1][j-group[i].size] + value[i])`,其中`group[i].size`表示第i个物品所属的分组包含的物品数量,`value[i]`表示第i个物品的价值。

4. 依次考虑物品和分组,更新`dp`数组。

5. 最终结果为`dp[n][m]`,其中n表示物品的个数,m表示分组的个数。

状态压缩解法:
1. 定义一个一维数组`dp`表示前i个物品选择j个分组所能获得的最大价值。

2. 初始化`dp`数组为0。

3. 设置状态转移方程为`dp[j] = max(dp[j], dp[j-group[i].size] + value[i])`,其中`group[i].size`表示第i个物品所属的分组包含的物品数量,`value[i]`表示第i个物品的价值。

4. 依次考虑物品和分组,更新`dp`数组。

5. 最终结果为`dp[m]`,其中m表示分组的个数。

dfa状态压缩表 简书

dfa状态压缩表 简书

dfa状态压缩表简书DFA(Deterministic Finite Automaton)即确定有限状态自动机,是一种常见的有限状态机模型。

状态压缩表是一种优化DFA实现的方法,它通过将多个状态转移合并为一个状态,从而减少状态的数量和转移的复杂度,提高了DFA的效率。

在DFA中,状态是有限的,每个状态对应着一个特定的输入字符,当接收到输入字符后,DFA会根据当前状态和输入字符进行状态转移。

通过一系列状态转移,DFA可以识别某个字母表中的字符串。

传统的DFA实现中,用的是状态转移表,它是一个二维数组,存储了每个状态对应的输入字符和下一个状态。

但是对于大规模的DFA来说,状态转移表会非常庞大,造成内存的浪费。

而状态压缩表则是一种压缩状态转移表的方法。

它基于DFA的特性,将多个状态转移合并为一个状态,从而减少了状态的数量。

状态压缩表通过使用位运算和压缩编码来优化DFA的实现,从而提高了运行效率。

状态压缩表的核心思想是合并具有相同转移关系的状态。

这种合并过程可以通过建立一个状态转移图来实现。

转移图中的每个节点代表一个状态,每条边代表一个状态转移。

通过深度优先搜索或广度优先搜索遍历这个转移图,我们可以找到具有相同转移关系的状态集合。

然后,我们可以将这些状态集合合并为一个新的状态,并将合并后的状态作为一个整体来处理。

在状态压缩表中,用一个整数来表示多个状态的状态集合。

这个整数的每一位对应一个状态,如果该位的值为1,则表示该状态在状态集合中。

在状态压缩表中,不同的状态集合可以使用不同的编码进行压缩。

最简单的方法是直接使用十进制或二进制表示状态集合,但是这样会造成编码的冗余。

因此,我们可以使用一种特殊的编码方式,将状态集合压缩为更短的编码。

一种常见的状态压缩编码方式是使用位运算。

我们可以使用一个整数表示状态集合,通过对这个整数进行位运算,来表示状态的集合关系。

例如,我们可以使用一个整数的每一位来表示一个状态是否在集合中,通过位与运算或位或运算来实现状态集合的合并和拆分。

状态转换序列

状态转换序列

状态转换序列状态转换序列是指在某个系统中,从一个状态到另一个状态的转换过程,这个过程中所经历的状态的序列。

在计算机科学中,状态转换序列是非常重要的概念,在许多领域都有应用,比如自动机理论、编译器构造、网络协议设计等等。

本文将从状态转换序列的概念、自动机的应用和实现、状态转换序列的优化等方面进行探讨。

一、状态转换序列的概念状态转换序列是指在一个系统中,从一个状态到另一个状态的转换过程,这个过程中所经历的状态的序列。

在计算机科学中,状态转换序列是非常重要的概念,在许多领域都有应用,比如自动机理论、编译器构造、网络协议设计等等。

在自动机理论中,状态转换序列通常指的是从一个初始状态到一个终止状态所经过的状态序列。

自动机是一种抽象的计算模型,它可以接受一些输入,然后根据输入的不同,转移到不同的状态。

自动机可以分为有限自动机和无限自动机两种,有限自动机是指状态数量有限的自动机,无限自动机是指状态数量无限的自动机。

有限自动机通常用于识别一些特定的字符串或者语言,而无限自动机则用于处理无限序列或者无限语言。

在编译器构造中,状态转换序列通常指的是编译器的语法分析过程中,从一个状态到另一个状态的转换过程。

语法分析是编译器的一个重要组成部分,它的功能是将源程序转换为中间代码或者目标代码。

语法分析的过程通常是由一个语法分析器完成的,语法分析器根据输入的源程序,从一个状态转移到另一个状态,最终得到一个语法树或者语法图。

在网络协议设计中,状态转换序列通常指的是从一个协议状态到另一个协议状态的转换过程。

网络协议是计算机网络中的一种通信协议,它规定了计算机之间的通信方式和数据传输格式。

在网络协议中,通常会有多个状态,每个状态都对应着协议的不同阶段,协议状态之间的转换通常由事件触发。

二、自动机的应用和实现自动机是一种抽象的计算模型,它可以接受一些输入,然后根据输入的不同,转移到不同的状态。

自动机可以分为有限自动机和无限自动机两种,有限自动机是指状态数量有限的自动机,无限自动机是指状态数量无限的自动机。

状压DP(超详细!!!)

状压DP(超详细!!!)

状压DP(超详细)⼀、定义总述状态压缩动态规划,就是我们俗称的状压DP,是利⽤计算机⼆进制的性质来描述状态的⼀种DP⽅式。

很多棋盘问题都运⽤到了状压,同时,状压也很经常和BFS及DP连⽤。

状压dp其实就是将状态压缩成2进制来保存其特征就是看起来有点像搜索,每个格⼦的状态只有1或0 ,是另⼀类⾮常典型的动态规划举个例⼦:有⼀个⼤⼩为n*n的农⽥,我们可以在任意处种⽥,现在来描述⼀下某⼀⾏的某种状态:设n = 9;有⼆进制数 100011011(九位),每⼀位表⽰该农⽥是否被占⽤,1表⽰⽤了,0表⽰没⽤,这样⼀种状态就被我们表⽰出来了:见下表所以我们最多只需要 2^(n + 1) - 1的⼗进制数就好(⼆进制形式是n个1)现在我们有了表⽰状态的⽅法,但⼼⾥也会有些不安:上⾯⽤⼗进制表⽰⼆进制的数,枚举了全部的状态,DP起来复杂度岂不是很⼤?没错,状压其实是⼀种很暴⼒的算法,因为他需要遍历每个状态,所以将会出现2^n的情况数量,不过这并不代表这种⽅法不适⽤:⼀些题⽬可以依照题意,排除不合法的⽅案,使⼀⾏的总⽅案数⼤⼤减少从⽽减少枚举为了更好的理解状压dp,⾸先介绍位运算相关的知识。

1. ’&’符号,x&y,会将两个⼗进制数在⼆进制下进⾏与运算(都1为1,其余为0)然后返回其⼗进制下的值。

例如3(11)&2(10)=2(10)。

2. ’|’符号,x|y,会将两个⼗进制数在⼆进制下进⾏或运算(都0为0,其余为1)然后返回其⼗进制下的值。

例如3(11)|2(10)=3(11)。

3. ’^’符号,x^y,会将两个⼗进制数在⼆进制下进⾏异或运算(不同为1,其余为0)然后返回其⼗进制下的值。

例如3(11)^2(10)=1(01)。

4. ’~’符号,~x,按位取反。

例如~101=010。

5. ’<<’符号,左移操作,x<<2,将x在⼆进制下的每⼀位向左移动两位,最右边⽤0填充,x<<2相当于让x乘以4。

NOIP2008提高组复赛题解

NOIP2008提高组复赛题解

void Output() {
ofstream fout(O_F); fout<<f[n-2][m-1][n-1]<<endl;
//不要输出f[n-1][m-1][n-1],正确 的方程是不会计算这个状态的
fout.close(); }
参考样程
long max(long a, long b) {
if (a>b) return a;
样例1 输入:
error 输出:
Lucky Word 2 解释: 单词error中出现最多的字母r出现了3次,出现 次数最少的字母出现了1次,3-1=2,2是质数。
样例2 输入:
olymipic 输出:
No Answer 0 解释: 单词olympic中出现最多的字母i出现了2次,出现次 数最少的字母出现了1次,2-1=1,1不是质数。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小 轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一 次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在 小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度 有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表 示),可以用一个0-100的自然数来表示,数越大表示越好心。小 渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到 来回两条传递路径,使得这两条路径上同学的好心程度只和最大。 现在,请你帮助小渊和小轩找到这样的两条路径。
NOIP2008提高组复赛题解
河南省实验中学 彭勃
第一题 笨小猴
* 题目描述:
笨小猴的词汇量很小,所以每次做英语选择题的时 候都很头疼。但是他找到了一种方法,经试验证明,用 这种方法去选择选项的时候选对的几率非常大! 这种方 法的具体描述如下:假设maxn是单词中出现次数最多的 字母的出现次数,minn是单词中出现次数最少的字母的 出现次数,如果maxn-minn是一个质数,那么笨小猴就认 为这是个Lucky Word,这样的单词很可能就是正确的答 案。
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

状态压缩Abstract信息学发展势头迅猛,信息学奥赛的题目来源遍及各行各业,经常有一些在实际应用中很有价值的问题被引入信息学并得到有效解决。

然而有一些问题却被认为很可能不存在有效的(多项式级的)算法,本文以对几个例题的剖析,简述状态压缩思想及其应用。

Keywords状态压缩、集合、Hash、NPCContentIntroducti o n作为OIers,我们不同程度地知道各式各样的算法。

这些算法有的以O(logn)的复杂度运行,如二分查找、欧几里德GCD算法(连续两次迭代后的余数至多为原数的一半)、平衡树,有的以O(n)运行,例如二级索引、块状链表,再往上有O(n)、O(n p log q n)……大部分问题的算法都有一个多项式级别的时间复杂度上界1,我们一般称这类问题2为P类(deterministic Polynomial-time)问题,例如在有向图中求最短路径。

然而存在几类问题,至今仍未被很好地解决,人们怀疑他们根本没有多项式时间复杂度的算法,它们是NPC(NP-Complete)和NPH(NP-Hard)类,例如问一个图是否存在哈密顿圈(NPC)、问一个图是否不存在哈密顿圈(NPH)、求一个完全图中最短的哈密顿圈(即经典的Traveling Salesman Problem货郎担问题,NPH)、在有向图中求最长(简单)路径(NPH),对这些问题尚不知有多项式时间的算法存在。

P和NPC都是NP(Non-deterministic Polynomial-time)的子集,NPC则代表了NP类中最难的一类问题,所有的NP类问题都可以在多项式时间内归约到NPC问题中去。

NPH包含了NPC和其他一些不属于NP(也更难)的问题(即NPC是NP与NPH的交集),NPC问题的最优化版本一般是NPH的,例如问一个图是否存在哈密顿圈是NPC的,但求最短的哈密顿圈则是NPH的,原因在于我们可以在多项式时间内验证一个回路是否真的是哈密顿回路,却无法在多项式时间内验证其是否是最短的,NP类要求能在多项式时间内验证问题的一个解是否真的是一个解,所以最优化TSP问题不是NP的,而是NPH的。

存在判定性TSP问题,它要求判定给定的完全图是否存在权和小于某常数v的哈密顿圈,这个问题的解显然可以在多项式时间内验证,1请注意,大O符号表示上界,即O(n)的算法可以被认为是O(n2)的,O(n p log q n)可以被认为是O(n p+1)的。

2在更正式的定义中,下面提到的概念都只对判定性问题或问题的判定版本才存在。

Levin给出了一个适用因此它是NP的,更精确地说是NPC的。

1如上所述,对于NPC和NPH问题,至今尚未找到多项式时间复杂度的算法。

然而它们的应用又是如此的广泛,我们不得不努力寻找好的解决方案。

毫无疑问,对于这些问题,使用暴力的搜索是可以得到正确的答案的,但在信息学竞赛那有限的时间内,很难写出速度可以忍受的暴力搜索。

例如对于TSP问题,暴力搜索的复杂度是O(n!),如此高的复杂度使得它对于高于10的数据规模就无能为力了。

那么,有没有一种算法,它可以在很短的时间内实现,而其最坏情况下的表现比搜索好呢?答案是肯定的——状态压缩(States Compression,SC)。

作为对下文的准备,这里先为使用Pascal的OIers简要介绍一下C/C++样式的位运算(bitwise operation)。

以上各运算符的优先级从高到低依次为:二、特殊应用a)and:i.用以取出一个数的某些二进制位ii.取出一个数的最后一个1(lowbit)2:x&-xb)or :用以将一个数的某些位设为1c)not:用以间接构造一些数:~0=4294967295=232-1d)xor:i.不使用中间变量交换两个数:a=a^b;b=a^b;a=a^b;ii.将一个数的某些位取反有了这些基础,就可以开始了。

1不应该混淆P、NP、NPC、NPH的概念。

这里只是粗略介绍,详见关于算法分析的书籍,这会使新手读者的理论水平上一个台阶。

弄不明白也没关系,基本不影响对本文其他部分的理解^_^2具有同样作用的还有(x-1)&x^x,这个的道理更容易理解。

lowbit在树状数组(某种数据结构)中可以用到,这里不再单独介绍,感兴趣的可以参阅各牛的论文,或者加我QQ。

建议掌握,否则可能会看不懂我Getting Started我们暂时避开状态压缩的定义,先来看一个小小的例题。

【引例】1在n*n(n≤20)的方格棋盘上放置n个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。

【分析】这个题目之所以是作为引例而不是例题,是因为它实在是个非常简单的组合学问题:我们一行一行放置,则第一行有n种选择,第二行n-1,……,最后一行只有1种选择,根据乘法原理,答案就是n!。

这里既然以它作为状态压缩的引例,当然不会是为了介绍组合数学。

我们下面来看另外一种解法:状态压缩递推(States Compressing Recursion,SCR)。

我们仍然一行一行放置。

取棋子的放置情况作为状态,某一列如果已经放置棋子则为1,否则为0。

这样,一个状态就可以用一个最多20位的二进制数2表示。

例如n=5,第1、3、4列已经放置,则这个状态可以表示为01101(从右到左)。

设f[s]为达到状态s的方案数,则可以尝试建立f的递推关系。

考虑n=5,s=01101。

这个状态是怎么得到的呢?因为我们是一行一行放置的,所以当达到s时已经放到了第三行。

又因为一行能且仅能放置一个车,所以我们知道状态s一定来自:①前两行在第3、4列放置了棋子(不考虑顺序,下同),第三行在第1列放置;②前两行在第1、4列放置了棋子,第三行在第3列放置;③前两行在第1、3列放置了棋子,第三行在第4列放置。

这三种情况互不相交,且只可能有这三种情况。

根据加法原理,f[s]应该等于这三种情况的和。

写成递推式就是:f[01101]=f[01100]+f[01001]+f[00101]根据上面的讨论思路推广之,得到引例的解决办法:f[0]=1f[s]=∑f[s^2i]其中s∈[0…01,1…11],s的右起第i+1位为1。

3反思这个算法,其正确性毋庸置疑(可以和n!对比验证)。

但是算法的时间复杂度为O(n2n),空间复杂度O(2n),是个指数级的算法,比循环计算n!差了好多,它有什么优势?较大的推广空间。

4Sample Problems【例1】5在n*n(n≤20)的方格棋盘上放置n个车,某些格子不能放,求使它们不能互1本文所有例题的C++代码均可以在附件中找到。

2方便、整齐起见,本文中不在数的后面标明进制。

3考虑上节介绍的位运算的特殊应用,可以更精巧地实现。

4还有一个很…的用处,即对新手说:“来看看我这个计算n!的程序,连这都看不懂就别OI了~”相攻击的方案总数。

【分析】对于这个题目,如果组合数学学得不够扎实,你是否还能一眼看出解法?应该很难。

对于这个题目,确实存在数学方法(容斥原理),但因为和引例同样的理由,这里不再赘述。

联系引例的思路,发现我们并不需要对算法进行太大的改变。

引例的算法是在枚举当前行(即s 中1的个数,设为r)的放置位置(即枚举每个1),而对于例1,第r 行可能存在无法放置的格子,怎么解决这个问题呢?枚举1的时候判断一下嘛!事实的确是这样,枚举1的时候判断一下是否是不允许放置的格子即可。

但是对于n=20,O(n2n )的复杂度已经不允许我们再进行多余的判断。

所以实现这个算法时应该应用一些技巧。

对于第r 行,我们用a[r]表示不允许放置的情况,如果某一位不允许放置则为1,否则为0,这可以在读入数据阶段完成。

运算时,对于状态s ,用tmps=s^a[r]来代替s 进行枚举,即不枚举s 中的1转而枚举tmps 中的1。

因为tmps 保证了无法放置的位为0,这样就可以不用多余的判断来实现算法,代码中只增加了计算a 数组和r 的部分,而时间复杂度没有太大变化。

这样,我们直接套用引例的算法就使得看上去更难的例1得到了解决。

你可能会说,这题用容斥原理更快。

没错,的确是这样。

但是,容斥原理在这题上只有当棋盘为正方形、放入的棋子个数为n 、且棋盘上禁止放置的格子较少时才有简单的形式和较快的速度。

如果再对例1进行推广,要在m*n 的棋盘上放置k 个车,那么容斥原理是无能为力的,而SCR 算法只要进行很少的改变就可以解决问题1。

这也体现出了引例中给出的算法具有很大的扩展潜力。

棋盘模型是状态压缩最好的展示舞台之一。

下面再看几个和棋盘有关的题目。

【例2】2给出一个n*m 的棋盘(n 、m ≤80,n*m ≤80),要在棋盘上放k(k ≤20)个棋子,使得任意两个棋子不相邻。

每次试验随机分配一种方案,求第一次出现合法方案时试验的期望次数,答案用既约分数表示。

【分析】显然,本题中的期望次数应该为出现合法方案的概率的倒数,则问题转化为求出现合法方案的概率。

而概率=方案总数合法方案数,方案总数显然为C(n*m,k),则问题转化为求合法方案数。

整理一下,现在的问题是:在n*m 的棋盘上放k 个棋子,求使得任意两个棋子不相邻的放置方案数。

这个题目的状态压缩模型是比较隐蔽的。

观察题目给出的规模,n 、m ≤80,这个规模要想用SC 是困难的,若同样用上例的状态表示方法(放则为1,不放为0),280无论在时间还是在空间上都无法承受。

然而我们还看到n*m ≤80,这种给出数据规模的方法是不多见的,有什么玄机呢?能把状态数控制在可以承受的1如果这样改编,则状态的维数要增加,如有疑问可以参考下面的几个例子,这里不再赘述。

范围吗?稍微一思考,我们可以发现:9*9=81>80,即如果n,m都大于等于9,将不再满足n*m≤80这一条件。

所以,我们有n或m小于等于8,而28是可以承受的。

我们假设m≤n(否则交换,由对称性知结果不变)n是行数m是列数,则每行的状态可以用m位的二进制数表示。

但是本题和例1又有不同:例1每行每列都只能放置一个棋子,而本题却只限制每行每列的棋子不相邻。

但是,上例中枚举当前行的放置方案的做法依然可行。

我们用数组s[1..num]1保存一行中所有的num个放置方案,则s数组可以在预处理过程中用DFS求出,同时用c[i]保存第i个状态中1的个数以避免重复计算。

开始设计状态。

如注释一所说,维数需要增加,原因在于并不是每一行只放一个棋子,也不是每一行都要求有棋子,原先的表示方法已经无法完整表达一个状态。

我们用f[i][j][k]表示第i行的状态为s[j]且前i行已经放置了k个棋子2的方案数。

沿用枚举当前行方案的做法,只要当前行的方案和上一行的方案不冲突即可,“微观”地讲,即s[snum[i]]和s[snum[i-1]]没有同为1的位,其中snum[x]表示第x行的状态的编号。

相关文档
最新文档