状态压缩dp
状态压缩是设计dp状态的一种方式。
当普通的dp状态维数很多(或者说维数与输入数据有关),但每一维总
量很少是,可以将多维状态压缩为一维来记录。
这种题目最明显的特征就是: 都存在某一给定信息的范围非常小(在20
以内),而我们在dp中所谓压缩的就是这一信息。
(或者是在做题过程中分析出了某一信息种类数很少)
我们来看个例子
经典题
给出一个n*m的棋盘,要放上一些棋子,要求不能有任意两个棋子相邻。
求方案数。
n<=100;
m<=8。
设(dp[i][s]) 表示第i行的状态 s是这一行棋子的排列压缩而成的二进制数,1表示有棋子,0表示没有棋子;
转移:(dp[i][s]=sum_{s'}dp[i-1][s']|s& s'=0)
我们发现这个m是非常小的,这样就可以启发我们对每一行2^m状态压缩。
设dp[i][S]表示到了第i行,第i行的状态是S的方案数是多少。
其中S的第j位为1,表示i这行第j位放了一个棋子。
状态转移:(dp[i][S]=sum_{S&S'==0}{dp[i-1][ S' ]})。
你会发现这样记录很暴力,状态数是与m相关的指数级的,但同时也就是因为m小我们就确实可以这么做。
这也正好映照了之前的:
其实本质就是很暴力的记录状态,只不过利用了题目本身的特殊条件(这一维很小),使得我们并不会因此复杂度过高。
同时也就是说,如果题目本身没有这样一个较小的信息,就不能应用状态压缩。
所以在接下来的题目中大家可以更注意一下题目所给的条件。
状态压缩dp肯定是有一维是指数级的,这正是状态压缩的特点。
BZOJ1087 互不侵犯
在n*n的棋盘上放置k个国王,使得任意两个国王互相不攻击。一个国王可以攻击到周围八个格子。
求放置方案数。
n<=9
(dp[i][j][s])表示第i行s状态,之前放了j个国王的方案数;
(dp[i][j+cnt(s)][s]=sum_{(s'|(s'<<1)|(s'>>1))&s==0}dp[i-1][j][s'])
状态s与状态s'的转移:进行左移和右移。&3次,分别为不变,左移,右移;
更简单的思路(s|(s<<1)|(s>>1))&s'==0;
时间复杂度 (O(n^3*状态数^2)<O(n^3*2^(2n)))
位运算
1、 (s & (1 << i)):判断第 i 位是否是 1;
2、 s =s|(1<<i):把第 i 位设置成 1;
3、 s =s&(~(1<<i)):把第 i 位设置成 0;
(~:是按位取反,包括符号位)
4、 s =s^(1<<i) :把第 i 位的值取反;
5、 s =s & (s – 1):把一个数字 s 二进制下最靠右的第一个 1 去掉;
6、 for (s0 = s; s0; s0 = (s0 - 1) & s): 依次枚举 s 的子集;
复杂度: 对于一个共有n位有k个1的二进制数s,它的子集的个数(C_n^k)
那么对于1~n的所有情况:ans=(sumlimits_{k=0}^nC_n^k*2^k*1^{n-k}=(1+2)^n)二项式定理
(2^k)表示k个1的所有可能情况
7: x&-x 取出x最低位的1。
拓扑序个数问题
给你一张拓扑图,求这张拓扑图有多少种不同的拓扑序.
n<=20。
dp[s]表示当前s集合中的点都已经在拓扑序中的方案数
转移考虑枚举下一个点选什么,下一个选的点要满足它在s中的点选完后的入度为0,也就是指向它的点都已经加进拓扑序(状态s)里了,转移到dp[s|(1<<i-1)] 。
处理一个数组in[u]表示其他哪个点指向u,然后转移的话就是保证s&in[u]=in[u];
bzoj2734
给定n,问集合{1,2,3,…,n}有多少个子集满足:若x在集合内,则2x和3x不在集合内。
n<=100000
(q*2^x*2^y)
记g[x]表示将x中的2,3因子去除后得到的值 (即q),记g[x]表示将x中的2,3因子去除后得到的值,若g[x]!=g[y],那么x与y互不影响。对于互相有影响的一组数,一定能表示成q*2^a^ *3^b^(q为常数)。对每种q分别求解,再相乘即可。
打表:
(1 3^1 3^2 3^3)
(2^1 2^1*3^1 2^1*3^2 2^13^3)
(2^2 2^2*3^1 2^2*3^2 2^2*3^3)
(2^3 2^3*3^1 2^3*3^2 2^3*3^3)
显然向右和向下会冲突,也就是右和下会互相冲突
时间复杂度分析:一行一行来看,因为最大n<=100000 ,那我们3最多大约是3^10^,因此共有11个状态0~10;所以s开11位?就可以了,时间复杂度约为f[11]*f[10]其中f[i]表示斐波那契数列第i项,转移啥的和t1很像。
NOIP2016 愤怒的小鸟
平面上有n头猪,每次可以从(0,0)出发发射一只沿抛物线(y=ax^2^+bx)飞行的小鸟,可以消灭所有在飞行路线上的猪。
问消灭所有猪至少要几只小鸟。
n<=18
两头猪加上原点即可确定抛物线,于是不同的抛物线只有O(n^2)种。
start:
确定抛物线:
猪的数量不是很多,每一条抛物线至少要打到一只猪,所以可以枚举出所有可行的抛物线,这条抛物线对每一只猪能/不能打下来用一个二进制为表示。
当然只能打到1只猪的抛物线不用枚举,一开始就加进去它的表示就行。
枚举两只猪,如果它们不在同一列,则将它们的坐标x,y分别代入方程y=ax^2^+bx,解得a,b,如果这组a,b没找到过且a为负,则用这组a,b尝试对每一只猪尝试看能不能打到(计算横坐标对应纵坐标)。
假设当前状态为i,抛物线为第j条,抛物线打掉的小猪状态为para[j],那么有:
(dp[i|para[j]]=min(dp[i|para[j]],dp[i]+1))
end.
start‘
设f[S]为已经消灭的猪的集合为S时的最少次数,暴力的转移方法为依次枚举抛物线去更新所有f,这样做时间复杂度为O(n^2^*2^n^)。
更快的转移方法为从小到大枚举S,每次打掉编号最小的还没消灭的猪,由于包含该猪的抛物线只有O(n)种,所以时间复杂度为O(n*2^n^)。
第i个抛物线:t[i]表示这条抛物线能消灭的猪是哪几只
dp[s]集合s中所有点都被消灭的最少抛物线条数;
在打0只小猪的时候,需要用0只小鸟,于是有:
dp[0]=0
dp[s]=min~i~{dp[s^t[i]]+1|t[i]&s=t[i],且t[i]包含s最低位}
首先s^t[i]表示没打这一坨小猪时的最小值,然后因为上面为了更快的算出来,所以我们要保证每次都要打掉编号最小的猪,然后找到所有状态中最小的吖+1,就是dp[s]的值w;
poj2923
有 n 个货物,并且知道了每个货物的重量,每次用载重量分别为c1,c2的火车装载,问最少需要运送多少次可以将货物运完。
N<=17
dp[s]表示s集合被c1,c2运走至少需要几次;
先处理出可以一次运走的情况,然后转移, 枚举子集:
dp[s]=min~t~{dp[s^t]+1|t&s=t};
找出所有状态(1.....(1<<n)-1)中选出可以用两辆车一次运完的状态(枚举全部)
把每个状态都看作一个物品,重量为该状态的总重量,价值为 1,其实我们就是求最少选几个不相交的集合能够覆盖全集。
求解 01 背包, dp[(1<<n)-1]为最优解。
转移方程: dp[s]=min(dp[s],dp[s^t]+1) 其中t集合内的元素可以被一次运完。
这个复杂度是O(3^n)。
实际上如果我们固定t集合包含s最低位的1,这样能避免重复运算,复杂度降约为O(3^(n-1))
Bzoj3900
动物园里有 n 头麋鹿。每头麋鹿有两支茸角,每支茸角有一个重量。然而,一旦某头麋鹿上两支茸角的重量之差过大,这头麋鹿就会失去平衡摔倒。为了不然这种悲剧发生,动物园院长决定交换某些茸角,使得任意一头麋鹿的两角重量差不超过 c。然而,交换两支茸角十分麻烦,不仅因为茸角需要多个人来搬运,而且会给麋鹿造成痛苦。因此,你需要计算出最少交换次数,使得任意一头麋鹿的两角重量差不超过 c。
注意,交换两支茸角只能在两头麋鹿之间进行。因为交换同一头麋鹿的两支角是没有意义的。
对于 100% 的数据, n <= 16, c <= 1000000, 每支茸角重量不超过1000000。
将所有鹿角排序,然后相邻两个做匹配,看相邻两个是否相差<=c;
设f[s]表示s这个集合的麋鹿,最少交换多少次可以使其合法;上界:f[s]=(cnt(s)-1)or inf;
f[s]=min~s'∈s~{f[s']+f[s^s']};
Hdu3001放松题
现在有一个具有n个顶点和m条边的无向图(每条边都有一个距离权值),小明可以从任意的顶点出发,他想走过所有的顶点而且要求走的总距离最小,并且他要求过程中走过任何一个点的次数不超过2次。
1<=n<=10
(dp[s][u])当前在u点,s状态第j位为0没有经过,为1经过1次,为2经过两次;
枚举下一个点v走哪里;
(dp[s+(1<<v)][v]=min {dp[s][u]+len[u][v]})
min (dp[s][u]),u=1~n
答案就是(dp[3^n-1][i])的最小值
BZOJ3717 Pakowanie
给出n个物品和m个包,物品有各自的体积,包有各自的容量。
问将所有物品装入包中需要至少几个包。
◦ n<=24, m<=100。
贪心来看显然要用尽量用最大的那几个, 所以先把包从大到小排序。
设f[S]为把集合S放入包后至少要用到第几个包 ,g[S]记录此时用到的最后一个包能剩余的最大体积。
转移时枚举接下来放哪个物品即可。
时间复杂度O(n*2^n)
只是口胡rwr,你让我写代码我一个都写不出来