状态压缩
•什么是状态压缩呢
•当一道题的状态很复杂,但是很少的时候,我们考虑暴力的状态表示出来
•绝大部分状压 DP 压缩的都是二进制,用来表示某些东西取/没取 这样的状态。
•指数级复杂度,适用于数据范围较小的题目
•通常会出现在棋盘问题中:
•比如说:[SCOI2005]互不侵犯、[NOI2001]炮兵阵地
•有的时候还要考虑如何优化减小状态数
二进制的使用
•查找第k位:(S>>k)&1
•将第k位置为1:S|=(1<
•将第k位置为0:S&=~(1<
•枚举子集:for(i=S;i&=S;--i);
•求二进制位上1的个数:for(r=0;x;++r,x&=(x-1));
•大概就这些,然后左移右移都是可以子集脑补的
例题A:[NOI2001]炮兵阵地
•https://www.luogu.org/problemnew/show/P2704
•在n*m的地上,有些格子是山地,有些格子是平原,可以在平原上部署部队,一个部队的攻击范围是沿横向左右各两格,沿纵向上下各两格。在互不侵犯的情况下,问最大的部署部队数 n≤100 ,≤10n≤100 ,m≤10
•状压dp基础题?根据
•表示上一行的状态是L,当前第行的状态是的最大答案f[L][S][i]表示上一行的状态是L,当前第i行的状态是S的最大答案
•每次枚举−1+来更新f[P][S][i-1]+count(S)来更新f[L][S][i] ,其中count其中count(S)表示S中包含1的个数,考虑如何满足题设的条件:
•左右的关系: S&S<<1=0 、S&S<<2=0
•上下的关系:S&L=0,S&P=0[bzoj 4197][Noi 2015]寿司晚宴
[SDOI2009]学校食堂
•https://www.luogu.org/problemnew/show/P2157
•N个人,每人有个口味T和忍耐度B(允许排在他之后的前B个人在他之前打饭),必须给每个人做他口味的菜,做每道菜的时间是(last or now)-(last and now),一开始last=0,最小化时间。≤1000,≤7n≤1000,B_i≤7
•f表示前−1个人打完菜,包括在内的之后7个人打饭状态为f[i][S][l]表示前i-1个人打完菜,包括i在内的之后7个人打饭状态为S
上一个打菜的是从−7位i-7位开始的第个,花费的最少时间的第l个,花费的最少时间,0≤≤150≤l≤15
•用刷表法
•如果S&1==0,也就是第i个人已经打过饭,则直接用它更新f+1≫1−1更新f[i+1][S≫1][l-1]
•否则,枚举还没取到的位k,更新f|(1≪)即可f[i][S|(1≪k)][k]即可
•当然还要注意判断当前没打饭的人当然还要注意判断当前没打饭的人是否都满足忍耐度限制
[bzoj 4197][Noi 2015]寿司晚宴
•题意
•从2~n中选出两个集合A和B,满足集合A的数均与集合B的数互质,询问方案数,选出的集合可以为空。
•≤500n≤500
•题解
•首先,集合A、B不相交,可以看成是用到的质因数集合不同
•可以把用到的质因数压成一个状态来dp
•dp[S1][S2][i]
•表示对于2~i,A、B集合用到的质因数状态为S1和S2的方案数
•若k为i的质因子集合,则有
•dp[S1|k][S2][i]+=dp[S1][S2][i−1] (k&S2==0) dp[S1][S2|k][i]+=dp[S1][S2][i−1] (k&S1==0)
•如果S1和S2按照从大到小的顺序更新,就可以省掉最后一维
但是,对于n≤500,质因子个数太多了,没办法表示出来n≤500,质因子个数太多了,没办法表示出来
发现对于大于等于23的质因子,每个数最多只有一个
——可以只对前面的2,3,5,7,11,13,17,19记录状态来dp
做法:
先将所有数按照它的最大质因数mx排序,对于mx小于20的直接dp即可
mx相同的数一起处理,再开两个数组1,2,表示全放在集合或是全放在集合的方案数tmp1,tmp2,表示全放在集合A或是全放在集合B的方案数
每处理完一个mx,更新dp数组:
dpS1S2=tmp1S1S2+tmp2S1S2−dpS1S2dp[S_1 ][S_2 ]=tmp1[S_1 ][S_2 ]+tmp2[S_1 ][S_2 ]-dp[S_1 ][S_2 ]
因为一开始tmp1、tmp2都继承了dp数组,所以最后还要减去一个dp[1][2]因为一开始tmp1、tmp2都继承了dp数组,所以最后还要减去一个dp[S_1][S_2]
概率
•小学的时候,它叫作“可能性”
•初中学的都是古典概型,表示为()=P(x)= 情况数/总情况数
• 条件概率?
•举个例子,已知两只兔子Pinkrabbit和GreenRabbit,某天,你偶然发现有只兔子的真实属性是个妹子,那么另一位兔子也是个妹子的概率是多少?
•三分之一
•∩=()P(A∩B)=P(A)P(B)
•∪=+−(∩)P(A∪B)=P(A)+P(B)-P(A∩B)
•P=∩() , P(A|B)表示P(A│B)=P(A∩B)/(P(B)) , P(A|B)表示B发生的情况下A发生的概率
•(|)=()(P(A│B))/(P(B|A))=P(A)/(P(B)) ,也叫贝叶斯公式
•全概率公式: =()P(A)=∑_i 〖P(A│B_i )P(B_i)〗
•设A、B为随机事件
•若P∩=(),P(A∩B)=P(A)P(B),则称A、B互为独立事件
•独立 ≠ 互斥≠ 互斥
•
•当随机变量的可取值全体为一离散集时称其为离散型随机变量。
• 可以用分布列来表示(掷骰子):
•性质:P ≥0 , =1=1P_i ≥0 , ∑129_(i ̇=1)^n▒〖P_i=1〗
•平均值(期望): Ex==1xiE(x)=∑129_(i ̇=1)^n▒〖P_i x_i 〗
•线性性质: E+=+()E(aX+bY)=aE(x)+bE(y)
•方差:D==1−2=2−2=平方D(x)=∑129_(i ̇=1)^n▒〖[x_i-E(x)]^2 P_i=E(x^2 )-[E(x)]^2 〗=平方的期望-期望的平方
•全期望公式:Ex= ,类似E(x)=∑_i▒〖P(y_i )E(x│y_i ) 〗,类似于全概率公式
•
B_i )P(B_i)〗
Hdu 4405 飞行棋
•N个格子,一开始在1号格子,每次掷骰子向前走,m个飞行点,走到它时直接飞到一个前方指定的格子,问走完N个格子掷骰子次数的期望
•N≤106,多组数据N≤〖10〗^6,多组数据
•
•期望/概率dp通常用到的逆推思想,很多时候,我们可以很容易的得到一个状态的后继以及相应的转移概率,但是却难以计算它前驱的贡献
•设[]f[i]表示到达i号点,还需要的期望步数
•若i是飞行点,=[[]]f[i]=f[nxt[i]]
•否者,=16=16[+]+1f[i]=1/6 ∑129_(j=1)^6▒〖f[i+j]〗+1
•若i≥ ,=0i≥n ,f[i]=0
•最后的答案是[0]f[0]
[bzoj 4318]OSU!
•一共有n次操作,每次操作只有成功与失败之分,成功对应11,失败对应00,n次操作对应长度为n的01串。在这个串中连续的x个11可以贡献3x^3的分数,这x个11不能被其他连续的11所包含
•现在给出n,以及每个操作的成功率p_i,请你输出期望分数
•
•N≤105N≤〖10〗^5
•首先有:
•+12−2=2+1 、 +13−3=32+3+1(x+1)^2-x^2=2x+1 、 (x+1)^3-x^3=3x^2+3x+1
•设u表示与这个位置相连的1串的期望长度 :=−1+1+0∗(1−) u_i 表示与i这个位置相连的1串的期望长度 :u_i=(u_(i-1)+1) p_i+0∗(1-p_i)
•设vi表示与这个位置相连的1串的长度平方的期望:vi=−1+2+1+02∗1−v_i 表示与i这个位置相连的1串的长度平方的期望:v_i=(v_(i-1)+2u_i+1) p_i+0^2∗(1-p_i )
•设i表示这个位置之前期望贡献:=−11−+−1+3+3+1f_i 表示i这个位置之前期望贡献:f_i=f_(i-1) (1-p_i )+(f_(i-1)+3v_i+3u_i+1) p_i
•将上面三个式子化简后分别递推即可,答案为f_n
[bzoj 3566][SHOI 2014]概率充电器
•由 n-1 条导线连通了 n 个充电元件。每条导线是否可以导电以概率决定,每一个充电元件自身是否成为电源也由概率决定。
•随后电能可以从电源元件经过通电的导线使得其他充电元件进行间接充电。
•进入充电状态的元件个数的期望是多少呢?
•N≤500000N≤500000
•
•题解
•
•题意:N个灯按照1~N标号,按下一个开关i,所有标号是i的约数的开关都改变状态,目标是关掉所有的灯,如果当前最优策略≤就直接按照最优策略走。≤k就直接按照最优策略走。否则随机按下一个开关。给出每个灯的当前状态,问期望步数*n!(mod 100003)
•N≤100000N≤100000
•
•首先可以直接N个开关的最优策略需要的步数t,(最大的状态为开的灯一定要按,以此类推)
•状态i表示当前的数按照最优策略需要i步
•最后的状态看成是0
•考虑f[i]表示从状态i到状态i-1的期望步数,最后答案是!∗=1 100003n!∗∑129_(i ̇=1)^t▒〖f[i] mod 100003〗
•当≤ 或者 =时,=1当i≤k 或者 i=n时,f[i]=1
•f=+−+1++1 <<f[i]=i/n+(n-i)/n (f[i+1]+f[i]+1) k
换根dp
•Luogu P3478 [POI2008]STA-Station
•题意:给定一棵n个点的无根树,你需要找一个点做根,使得所有点深度(即到根经过的边数)之和最大。
•≤100000n≤100000
•对于这种“挑一个最优的点”的题目,我们往往先找一个点,计算 出相应的值,然后考虑把一个点的值转移到相邻的点上。
•设f[i]表示以i为根的时候的答案。
•考虑u是v的父亲。并且f[u]的值已经被求出。
•根从 u 移动到 v,则 v 的子树中的点的深度都变小了1,其余点都变大了 1。即变化量为 −size[v]+n−size[v] = n−2size[v],其中 size[v] 为子树 v 内 的节点个数。 所以方程为 f[v] = f[u] +n−2size[v],根据方程进行 DP 即可。
数位dp
•顾名思义,就是基于数位的dp
•通常是让你对[a,b]之间的数进行某种统计
•最常见的做法:差分区间、枚举前若干位与上界相同
•
•举个例子?
•9021-1999 不要62
•bzoj1026 [SCOI2009]windy数
•题意:询问A、B之间的不含前导0且相邻两个数位的数字差至少为2的正整数
bzoj1026 [SCOI2009]windy数
•首先,把询问拆成询问[1,r]和[1,l-1]的数量相减
•[0/1]表示确定F[i][j][0/1]表示确定了前ii位,第i位是j,前i位是否等于n的前i位的方案数。转移时,枚举下一位即可。
••但是,如果用F数组来统计,会特别麻?用记搜就要轻松一些:
基环树
•基环树,也叫环套树,表现为n个点n条边的连通图
•显然基环树只有一个环,并且删掉环上任意一个边可以变成一棵树
•处理基环树问题一般先把环找出来再处理
Bzoj 1040 [zjoi2008]骑士
•每个骑士都有一个战斗力和最讨厌的人,不能与他同时出征
•已知每个人的战斗力和他最讨厌的人,要求组织一个军队,最大化战斗力
•N≤106N≤〖10〗^6
•
•基环树森林
•考虑如果没有环怎么做:记[0/1]F[i][0/1],表示以i为根的子树内,选i的最大战斗力
•如果是基环树,找到环后,任意去掉环上的一边,分别以边的两个端点a,b为根作树形dp,因为a,b中最多取一个,最后答案为max0,0.max(F[a][0],F[b][0]).
Bzoj 1791 [ioi2008]岛屿
•N个岛屿,每个岛屿都向外建一座长度为Li的桥,但桥是双向的,没有自环
•可以自行挑选一个岛开始游览,任何一个岛屿不能访问超过1次
•有步行和渡船两种方式,但是渡船必须满足终点在另一个联通块内,且每个联通块最多访问1次
•N≤106N≤〖10〗^6
•
•=基环树森林,求最长链之和
•对于每棵树,先找到环
•对环上所有点的子树求出它们的直径和最大深度,同时更新ans。
•考虑在环上至少经过一条边的路径。
•破环成链,是环变成一个长度为2*len的数组,
•设环上边权的前缀和为sum sum_i ,环上结点的子树的最大深度为depdep_i。
•在环上起始点为i,终点为j的路径能得到的长度:−++sum_j-sum_i+dep_j+dep_i 。
•枚举j,就只用维护−+-sum_i+dep_i的最大值就可以了。
•这个可以用单调队列实现。
•
•Dfs的时候记录一下它的上一个节点fa即可,找到一个已经访问过的不是fa节点,就找到环了辣,而对于上面的两题,因为都是基环内向树,所以找环可以很容易的实现: