清北学堂2019.8.8
Day 3 赵和旭
状态压缩dp
状态压缩是设计dp状态的一种方式。
当普通的dp状态维数很多(或者说维数与输入数据有关),但每一维总量很少是,可以将多维状态压缩为一维来记录。
这种题目最明显的特征就是:都存在某一给定信息的范围非常小(在20以内),而我们在dp中所谓压缩的就是这一信息。(或者是在做题过程中分析出了某一信息种类数很少)
经典题
给出一个n*m的棋盘,要放上一些棋子,要求不能有任意两个棋子相邻。
求方案数。n<=100,m<=8。
通过dp[i][s] s的二进制表示如何放
则相邻行可以表示dp[i][s’]
满足性质时s&s'==0
BZOJ1087 互不侵犯
在n*n的棋盘上放置k个国王,使得任意两个国王互相不攻击。一个国王可以攻击到周围八个格子。
求放置方案数。n<=9
f[i][k][S]表示k个放完前i行,第i行的放置状态为S的方案数。
S为一个n位二进制数,表示第i行的每一位上是否有棋子。
由于棋子不能相邻,也就是这个二进制不能有相邻的1,所以可以先对求出对一行来说合法的状态(合法的二进制数有哪些)。
转移:f[i][k+cnt(S)][S]+=f[i-1][k][T],( (T|(T<<1)|(T>>1)) & S )==0 (巧用位运算!)
时间复杂度O(n^3*状态数^2)<O(n^3*2^(2n))(实际上状态数是 Fibonacci)
位运算
1、(s & (1 << i)):判断第 i 位是否是 1; 7: x&-x 取出x最低位的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 的子集;
for (int s=1;s<(1<<n);s++) for (int s0=s;s0;s0=(s0-1)&s)
复杂度O(3^n)
拓扑序个数问题
给你一张拓扑图,求这张拓扑图有多少种不同的拓扑序.n<=20。
dp[s]表示当前s集合中的点都已经在拓扑序中的方案数,转移考虑枚举下一个点选什么,下一个选的点要满足它在s中的点选完后的入度为0,也就是指向它的点都已经加进拓扑序里了,转移到dp[s|(1<<i-1)] 。考虑位运算优化
bzoj2734
给定n,问集合{1,2,3,…,n}有多少个子集满足:若x在集合内,则2x和3x不在集合内。n<=100000
记g[x]表示将x中的2,3因子去除后得到的值,若g[x]!=g[y],那么x与y互不影响。对于互相有影响的一组数,一定能表示成q*2^a*3^b(q为常数)。对每种q分别求解,再相乘即可。
以q=1为例,可把所有2^a*3^b以a,b为行列画成一个三角形棋盘,求棋盘上放不能相邻的棋子的方案数。我们会发现这个三角形最宽的层最多只有11个,所以可以状态压缩。
这也就对应了——或者是在做题过程中分析出了某一信息种类数很少。
当q>1,等价于q'=1&&n'=n/q。
剪枝:对于n/q相同的q,可以避免重复计算来加速。
NOIP2016 愤怒的小鸟
平面上有n头猪,每次可以从(0,0)出发发射一只沿抛物线(y=ax^2+bx)飞行的小鸟,可以消灭所有在飞行路线上的猪。
问消灭所有猪至少要几只小鸟。n<=18
两头猪加上原点即可确定抛物线,于是不同的抛物线只有O(n^2)种。
设f[S]为已经消灭的猪的集合为S时的最少次数,暴力的转移方法为依次枚举抛物线去更新所有f,这样做时间复杂度为O(n^2*2^n)。
更快的转移方法为从小到大枚举S,每次打掉编号最小的还没消灭的猪,由于包含该猪的抛物线只有O(n)种,所以时间复杂度为O(n*2^n)。
其实就避免一些重复的计算。
dp[s]=min{dp[s^t]+1|t&s==t}
poj2923
有 n 个货物,并且知道了每个货物的重量,每次用载重量分别为c1,c2的火车装载,问最少需要运送多少次可以将货物运完。N<=17
找出所有状态(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)) 枚举子集3^n也是很常见的状压dp!!
t[s]=|f[s’]&g[s^s’]
Bzoj3900
动物园里有 n 头麋鹿。每头麋鹿有两支茸角,每支茸角有一个重量。然而,一旦某头麋鹿上两支茸角的重量之差过大,这头麋鹿就会失去平衡摔倒。为了不然这种悲剧发生,动物园院长决定交换某些茸角,使得任意一头麋鹿的两角重量差不超过 c。然而,交换两支茸角十分麻烦,不仅因为茸角需要多个人来搬运,而且会给麋鹿造成痛苦。因此,你需要计算出最少交换次数,使得任意一头麋鹿的两角重量差不超过 c。
注意,交换两支茸角只能在两头麋鹿之间进行。因为交换同一头麋鹿的两支角是没有意义的
首先其实最终肯定是把这些鹿分成一些组,每一组内通过组的大小减一次操作来满足题目要求的条件。注意对于一个组,我们将所有的角排序,第2*i-1和2*i个要保证之差小于等于C,才是合法的一组。
其实就是选尽量多合法的组并起来等于全集,枚举子集的状态压缩dp即可。
dp[i]=max{dp[j]+dp[i^j] | j是i的一个子集}。
Hdu3001
现在有一个具有n个顶点和m条边的无向图(每条边都有一个距离权值),小明可以从任意的顶点出发,他想走过所有的顶点而且要求走的总距离最小,并且他要求过程中走过任何一个点的次数不超过2次。
1<=n<=10
设dp[S][i]表示到过点的情况集合为S,S是个三进制数,第i为0/1/2分别表示到过次数。i为当前所在的点。转移的话就考虑下一步走到那条边就好。
答案就是dp[3^n-1][i]的最小值。
会二进制的表示,三进制想起来应该很自然吧。
其实是TSP旅行商问题的一种较为麻烦的形式。
关于时间复杂度
N=20一般是2^n或者n*2^n。
N<=16大概率是3^n,约是4*10^7,那很可能做法就和之前讲枚举子集有关了。
N<=15大概率是3^n或者n*3^n。
BZOJ3717 Pakowanie
给出n个物品和m个包,物品有各自的体积,包有各自的容量。
问将所有物品装入包中需要至少几个包。
n<=24,m<=100。
贪心来看显然要用尽量用最大的那几个,所以先把包从大到小排序。
设f[S]为把集合S放入包后至少要用到第几个包,g[S]记录此时用到的最后一个包能剩余的最大体积。
转移时枚举接下来放哪个物品即可。
时间复杂度O(n*2^n)
DP优化方法
逆序对
求长度为n,逆序对为k的排列有多少个,答案对10^9+7取模。
1≤n,k≤200
1≤n,k≤2000
排列题的一个套路,我们从小到大依次把数字插入到排列中,以这个思路进行dp。
这个问题设计动态规划的方法就是最基本,最自然的。
我们设dp[i][j]表示插入了前i个数,产生的逆序对为j的排列的方案数,
转移时就考虑i+1的那个数插在哪一个位置就好,因为它比之前的都要大,插在最后面就dp[i+1][j+0]+=dp[i][j],如果时最后一个数前面就是dp[i+1][j+1]+=dp[i][j],倒数第2个数前面就是dp[i+1][j+2]+=dp[i][j],依次类推。这个是从前向后更新。我们如果考虑dp[i][j]能从哪些状态转移过来,就可以前缀和优化。
方程:dp[i][j]=∑dp[i-1][j-k] (0≤k<i)
我们设:f[i][j]=∑dp[i][k] (0≤k<j)
则dp[i][j]=f[i-1][j]-f[i-1][j-i]
这样我们通过记录前缀和,将转移优化成O(1)
其实这题复杂度还可做到n和k均是100000:loj6077(2017年山东省队集训题)
在这个dp基础上,做容斥原理,通过之前讲的整数划分的模型dp求出容斥系数即可。
Bzoj1855:股票交易
我们考虑在未来T天内的某只股票,第i天的买入价为每股APi,第i天的卖出价为每股BPi(对于每个i,都有APi>=BPi),每天不能无限制地交易,规定第i天至多只能购买ASi股,至多卖出BSi股。另外规定在两次交易(买入或者卖出均算交易)之间,至少要间隔W天,也就是说如果在第i天发生了交易,那么从第i+1天到第i+W天,均不能发生交易。还规定在任何时间,一个人的手里的股票数不能超过MaxP。
初始w手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,问T天以后,小w最多能赚多少钱。
0<=W<T<=2000
设f[i][j]表示到第i天手里持有j的股票的最大收益,那么第i天有三种操作:
不买不卖:f[i][j]-f[i-1][j]
买入:f[i][j]=f[i-w-1][k]-ap[i]*(j-k)|k≥j-as[i]
卖出:f[i][j]=f[i-w-1][k]-bp[i]*(k-j)|k≤j+bs[i]
对于买入,我们对其变形:
f[i][j]=f[i-w-1][k]-ap[i]*(j-k)=f[i-w-1][k]+ap[i]*k-ap[i]*j
那么可以用单调队列维护f[i-w-1][k]+ap[i]*k(因为对于固定的i,ap[i]是固定的),这样f[i][j]就能做到O(1)求得,而不必枚举k。卖出也一样。
bzoj3831: [Poi2014]Little Bird
有一排n棵树,第i棵树的高度是Di。
MHY要从第一棵树到第n棵树去找他的妹子玩。
如果MHY在第i棵树,那么他可以跳到第i+1,i+2,...,i+k棵树。
如果MHY跳到一棵不矮于当前树的树,那么他的劳累值会+1,否则不会。
为了有体力和妹子玩,MHY要最小化劳累值。
N(2<=N<=1000000)
根据题意,我们很容易得出下面的转移方程
1.f[i]=min(f[j]+1) ( i-k≤j<i )
2.f[i]=min(f[j]) ( i-k≤j<i &&h[j]>h[i])
发现上面那个东西用单调队列直接搞定,但下面那个不太好搞。不过发现由于h[j]>h[i]对答案的贡献至多为1,所以原来如果f[j]<f[j’],那么算上h[j]和h[j’]的影响后j仍然不会比j’更差,于是直接维护一个f递增的单调队列,其中当f相同的时候使h递减就行了。
这么做,主要利用了题目中高和矮转移的费用只差1,这样如果矮的f[i]小的话,就算加上1也只是和高的f值相同不会更差。如果费用更高就不能用这个方法了。
送你一堆区间
送你在数轴上的 n 个区间和 m 个关键点, 你可以决定每个区间选或不选, 问有多少种方案覆盖 所有的关键点. 对1000000009取模.
N<=5*10^5
这三个技巧往往就是dp列出式子来之后,观察一下式子,你发现它满足对应优化的模型,所以我们就单调队列或者前缀和或者线段树优化了。
Noip2008传纸条
一个m行n列的矩阵,而小渊和小轩传纸条。纸条从小渊坐标(1,1),传到小轩坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。班里每个同学都可以帮他们传递,但只会帮他们一次。全班每个同学有一个好心程度。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
n,m<=300。
其实可以理解为从小渊到小轩传两次。
最暴力的做法:设dp[i][j][k][l]是第一张纸条到达(i,j),第二张到达(k,l)时最大权值。那么方程就是dp[i][j][k][l]=map[i][j]+map[k][l]+max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],dp[i][j-1][k-1][l],dp[i][j -1][k][l -1]);
还有一点注意的是,如果i == k && j == l,也就是第二张走了第一张的路径,那么就要减去,dp[i][j][k][l] -= map[i][j];
接下来枚举每个i,j,k,l,最后输出dp[m][n][m][n]就行了,但这样做时间复杂度是O(n^4),
其实我们可以让两个路线并行走,同时走。
而既然第一张与第二张是同时走,那么我们知道他们的步数是一样的,步数 = 横坐标+纵坐标,所以只需枚举i,j,k,就能计算出l,只需三重循环,时间就变成了O(n^3);
dp[i][j][k] = map[i][j] + map[k][i+j-k] + max(dp[i - 1][j][k -1],dp[i-1][j][k],dp[i][j-1][k-1],dp[i][j-1][k]);
下午考试就比较有趣了,hhh <(︶︿︶)>_╭∩╮╭∩╮