比赛的时候刷出来的第一个状态DP。(期间有点没有把握是状态DP呢。)
题意:题意还是简单的。K行的方格。之后输入L1~LK 代表每一行方格数。在这些往左紧挨的方格子里填上1~N的数字。
其中右边格子的数值会大于等于左边的格子,下边的格子的数值会大于上边的格子。
其中观察一列的数值。会发现一列的数值均是不一样的。而且 N<=7。也就是说我们让1~N的数字填到第一列上。那我们可以按列来进来状态压缩。也就是认为一列就是一个状态。
也就是我们让:
数值 7 6 5 4 3 2 1
二进制0 0 0 0 0 0 0
对应起来。0代表不存在 1代表存在。
这里有一个注意点:状态压缩并不能表示出状态内部元素的大小关系的。只能表示这个元素有与无。那么想处理下一个元素(方格的值)比上一个元素大,该如何是好?
比赛的时候思维混乱,在那里考虑能不能预处理这样的合法状态。但是明显没意义的,因为二进制只能表示有和无啊。完全不可能表示出内部元素大小关系的啊。
其实只要直接默认它已经存在这样的关系即可。(其实本来就该如此)。
比如: 1 1 1 1 1 1 1 1 (7个1) PS:这个数据并不大。而且这也是状态压缩的一个特点。20个元素(我喜欢把状态里面的各个数值叫做元素)还是可以接受的放进下标中。更何况就7个。
这个状态就表示这一列的排列顺序是 1 2 3 4 5 6 7 (从上到下)。
又比如: 1 0 1 0 1 1 1 代表从上到下的顺序是 1 2 3 5 7
接下来是后一列和前一列对应的元素大小比较 下后一列>=前一列。
我的处理方式是把后一列的状态一一提取元素和前一列的对应元素来比较。
提取元素的方法不多说。
设后列的状态是 i
那么对应元素就是 i&(-i) 。每一次比较之后 i -= i&(-i).
编程之美上面是 i &= (i-1) 来删除 最后一位的1。说真的也很美。题外话。具体数学上。第一章对这个二进制的处理也真的是美爆了。
并且在这个时候 统计当前层的元素个数。元素个数(1的个数)如果大于当前列的方格子数当然是非法的状态啦。就不能转移上来了。
接下来就是码状态压缩了。
个人思考步骤:
1:处理出每一列的合法状态 (这个题目就是一个循环 1 ~ 1<<N-1 即可)
2:思考状态所需要记录的信息。前后列状态转移(感觉可以滚动数组啊。但是列数不多,而且现在我技术还是比较有限的)。所以抽象出dp[i][j] 第i列的j状态。为了后列能根据前列的信息得出而已。
3:初始化。也就是对第一列做初始化。
4:循环层数。 这里有3层 一层是列。一层是当前层的状态 。再一层是当前层的上一层的状态。(如果题目和上上层也有关系的话。那就再多一层循环。反正我们状态的信息都记录进了dp[i][j]里了。循环是在使用它们)。
这是我当前的能力所能达到的理解。
思考上的小bug:
1:会觉得 1~1<<N-1 里面还有很多的非法状态啊。比如说 k=3 那么 1~1<<N-1 只有二进制的数字 1的数目为3的是合法的。可是我在写循环的时候上一层的状态。我都是直接从 1~1<<N-1 这种写法。那非法状态不也是转移上来了?
其实看似转移上来了。其实不然。我们并不需要煞费苦心还要对上一层的状态进行统计1 然后排除非法之类的。因为非法的状态。即使转移上来 也是0 也就是说它对当前状态是不会有贡献的。这也是经典背包问题上满包为何要把值弄成-INF的原因。
这里的-INF 要看情况的。比如概率运算的话 。如果你弄成 -无穷大 那是会出现错误的。因为有乘积。
状态压缩题目的特点:
1:数值上会微妙得比较少。而且这个数值往往会只能出现一次。而且是根据出现与否就可以确定序列式如何的。 如果说这个题目改一改。变成一列上的数字要不同。那么 我们 0000111 可以表示是 1 2 3 或者是 3 2 1 或者是 2 1 3。反而导致我们无法使用状态压缩。而这种严格递增递减的序列恰恰是壮压发挥的好场所 (我们需要对题设敏感)而且求方案数是个老问题了。
比赛的时候第一次实践了状压开心不已。但是后来竟然被别人用搜索加剪枝给过了。
后来思考了一下:搜索应该可以这样考虑。就是右边的话 有 当前数值~N 个方向。向下的话有 当前数值+1 ~ N个方向。最后能把整个图给走满 就方案数+1。只能说他们的搜索太扎实了。。或者说我该考虑一下这种抽象的搜索。