Loading

状压dp

状态压缩dp

不知道咋滴,markdown好像炸了,怎么也调不过来/kk

状态压缩dp

是一种设计dp状态的一种方式

当普通的 \(dp\) 状态维数很多(或者维数和输入数据有关),但每一维总量很少,可以将多维状态压缩为一维来记录

特征

存在一给定信息范围非常小(在 20 以内),dp 中所压缩的就是这一信息(或者在做题过程中分析出了某一信息种类很少)

栗题

给出一个 \(n*m\) 的棋盘,要放上一些旗子,要求不能有任意两个棋子相邻,求方案数

\(n \leq 100~~~m \leq 8\)

solution

这个题发现 \(m\) 的取值范围特别小,考虑状压dp

状态

\(dp[i][s]\) 表示到了第 i 行,第 i 行的状态为 S 的方案是多少,S 的第 j 位为 1,表示第 i 这一行的第 j 位放了一个棋子

转移

\[dp[i][S] = \sum\lbrace dp[i-1][S'] | S~and~S' == 0\rbrace \]

很暴力的记录状态,只不过利用题目本身的特殊条件(这一维很小),使得我们复杂度不会过高,所以我们要注意题目给出的条件

状态压缩dp特点:有一维是指数级的

互补侵犯

\(n*n\) 的棋盘上放置 \(k\) 个国王,使得任意两个国王互相不攻击。一个国王 可以攻击到周围八个格子。

求放置方案数
\(n\leq9\)

solution

状态

\(f[i][j][S]\) 表示放完前i行,第 i 行的放置状态为 S 的方案数, j 个国王

S 为一个 n 位二进制数,表示第 i 行的每一位上是否有棋子

棋子不能相邻,所以二进制不能有相邻的 1,所以可以对先求出对一行来说合法的状态(枚举没有相邻 1 的二进制)

转移

\[f[i][j][S]+=f[i-1][j-cnt(S)][T]\\( (T|(T<<1)|(T>>1))~and~S )==0 \]

时间复杂度:\(O(n*状态数^2)<O(n*2^{2n})\)

实际上是斐波那契

位运算

位运算在状态压缩中是个好东西,计算机存数本来就是二进制,所以二的幂次方进制处理很方便,上题就不用枚举上每一位比较,只要与一下看有没有 1,就好了

常见运算

  • (s & (1 << i)):判断第 i 位是否是 1;
  • s =s|(1 << i):把第 i 位设置成 1;
  • s =s & (~(1 << i)):把第 i 位设置成 0; (~:是按位取反,包括符号位)
  • s =s ^ (1 << i) :把第 i 位的值取反;
  • s =s & (s – 1):把一个数字 s 二进制下最靠右的第一个 1 去掉;
  • for (s0 = s; s0; s0 = (s0 - 1) & s): 依次枚举 s 的子集;
for (int s = 1; s < (1 << n); s++)
  for (int s0 = s; s0; s0 = (s0 - 1) & s)

复杂度??

栗题

拓扑序个数

一张拓扑图,求这张拓扑图有多少种不同的拓扑序

\(n\leq 20\)

solution

状态

dp[s] 表示当前 s 集合中的点都已经都在拓扑序中的方案数;

转移

枚举下一个点选什么,满足它在 s 中的点选完后的入度为 0(指向它的点都已经在拓扑序中了)

转移

枚举入度为 0 的点转移到 dp[s|1 << i-1]

复杂度: \(O(2^n*n)\)

我们打算用 \(1 * 2\) 的骨牌去填满 \(N * M\) 的棋盘,问一共有多少种方案?

\(n\leq100,m\leq8\)

solution

状态:

\(f[i][state]\) 表示放置到第 i 行,第 i 行的状态为 state 的方案

state 是一个 m 位的 2 进制数,1 表示是一个竖着的骨牌上半部分,下面需要接骨牌,0 表示下面不需要接

转移:

枚举下一行的状态:next_state,如果next_state和state能正好合上说明符合条件则

\(f[i + 1][next_state] += f[i][state]\)

判断符合条件??

如果当前 state 的第 k 位是 1,那么此处必须接一个竖着的骨牌的下半部分,next_state 这一位必须是0。

如果当前 state 的第 k 位是 0,说明下一行的 next_state 状态对应位置是可以随意选的,可 1 可 0

满足的两个条件:

1:上下两个 1 不能挨着

2:同一行两个 1 之间的距离需要是偶数(中间不填或者横着填)

时间复杂度

\(O((2^m)^2*n)\)

愤怒的小鸟

平面上有 \(n\) 头猪,每次可以从 \((0,0)\) 出发发射一只沿抛物线\((y=ax^2+bx)\)飞行的小鸟,可以消灭所有在飞行路线上的猪。

问消灭所有猪至少要几只小鸟。
\(n\leq18\)

solution

两头猪再加上原点就可以确定抛物线,于是不同的抛物线也就有 \(O(n^2)\)

状态:

\(f[S]\) 为已经消灭的猪的集和为 S 时的最小次数

转移:

枚举抛物线去更新所有的 f,时间复杂度为 \(O(n^2*2^n)\)

还可以从小到大枚举 S,每次打掉编号小的还没有消灭的猪,有包含该猪的抛物线只有 \(O(n)\) 种,所以时间复杂度为 \(O(n*2^2)\)

\(n\) 头麋鹿。每头麋鹿有两支茸角,每支茸角有一个重量。一只麋鹿上两支茸角的重量不能相差过大,现在决定交换某些茸角,使得任意一头麋鹿的两角重量差不超过 \(c\) ,因此,你需要计算出最少交换次数,使得任意一头麋鹿的两角重量差不超过 c

注意,交换两支茸角只能在两头麋鹿之间进行

对于 100% 的数据,\(n \leq 16, c \leq 1000000\), 每支茸角重量不超过 1000000

solution

把鹿分成若干组,每一组内通过组的大小减一次操作来满足满足题目要求,对于每个组,对所有的角进行排序,第 \(2*i-1\) 和第 \(2*i\) 个要保证之差小于等于 c,才是合法的一组

选尽量多合法的组并起来等于全集,枚举子集状态压缩dp就好了

\[dp[i] = max\lbrace dp[j]+dp[i^j]|j\rbrace \]

j 是 i 的一个子集且 j 是一个合法的划分方案

总结

像这种某些集合是可行的,或者说每个集合有一个价值,然后我们要选 择一些不相交的集合并起来等于全集,每个选择的集合都要求可行,且 希望总权值尽量大

这一类 dp 往往是要用到状态压缩 dp 同时利用枚举子集来进行转移,转移 的过程中常常可以控制最低位的 1 必选来减少 dp 重复的计算。像这前两道题,以及之前的愤怒的小鸟也一样,只不过小鸟那题通过分析题目的特点使得我们在转移的过程中不需要枚举全部的子集,只需要枚举 n 个抛物线即可

栗题

现在有一个具有 n 个顶点和 m 条边的无向图(每条边都有一个距离权值), 从任意的顶点出发,走过所有的顶点而且要求走的总距离最小,走过任何一个点的次数不超过 2 次(TSP 旅行商问题)

\(1\leq n\leq10\)

solution

n 的范围很小,所以考虑把每个点状压,考虑每个点经过了几次

状态

\(dp[S][j]\) 表示到过这个点的情况的集合为 S,S 是个三进制数

第 i 为0/1/2分别表示到过次数,i 为当前所在的点

转移

考虑下一步到哪一条边

答案

\(dp[S][i]\) 的最小值

三进制的表示可以用两个二进制压;

LIS问题2

给出一个 \([1,n]\) 排列的其中一种最长上升子序列,求原序列可能的种数

\(n \leq 15\)

solution

状态:

\(f[i][j]\) 表示所选的数字的集合为 i,状态为 j;h[i] 表示长度为 i 的上升子序列结尾的最小值,数字集合 i 中的一些数出现在了 h 数组中, j 是出现在 h 中的数的集合;

转移:

如果在这个序列的末尾加上一个数 x,就会在 h 中把大于 x 最小的数替换掉,在 j 中就会把比 x 高位最近的 1 去掉,x 这位赋为 1 (位运算) 枚举下一个数是啥,如果这个数以前就出现过,就需要保证序列中它的前一个数也已经出现过了,从 \(f[i][j]\) 转移到 \(f[i + (1<<x)][k]\) 其中 k 表示转移后序列的状态

时间复杂度:\(O(n*3^n)\) 其中转移:\(O(n)\) 枚举子集 \(O(3^n)\)

时间复杂度

\(N=20\) 一般是 \(2^n或者n*2^n\)

\(N \leq 16\) 大概率是\(3^n\) ,约是 \(4*10^7\),那就很可能和之前枚举子集有关

\(N \leq 15\) 大概率是 \(3^n或者n*3^n\)

posted @ 2021-02-17 14:38  Dita  阅读(77)  评论(0)    收藏  举报