大意:
求 n(n<=1011) 以内恰好有 4 个因数的数的个数
分析:
首先一个数恰好有 4 个因数,说明它质因数分解之后是两个质数的乘积或是一个质数的三次方,对于后一种情况我们直接 n1/3 就能算出来,关键在于计算 n 以内有多少个数是两个素数的乘积。
设 n=p1∗p2 ,则必然有 p1<n−√,p2>n−√ ,我们枚举 p1 ,那么问题就在于 np1 内有多少个素数。
这一点我们用dp来解决。
记 pj 为第 j 个素数, dp[n][j] 为 [1,n] 以内最小质因子 >=pj 的数的数量,我们有
1.dp[n][1]=n
2.dp[n][j]=dp[n][j+1],dp[⌊n/pj⌋][j] ,
也就是说 dp[n][j+1]=dp[n][j]−dp[⌊n/pj⌋][j]
令 pk 是最小的大于 n−√ 的素数,那么 π(n)=dp[n][k]+k−1
大意:
给 n ( n≤106 )个长度为 hi 的竖方格,允许取走连续若干个竖方格的顶部(不允许取最底层),求方案数
分析:
记 dp[l][r] 为取走的区间为 [l,r] 的方案数,总方案数就是
大意:
给一个 n∗n 的矩阵,每个格子有一个数,从左上到右下,只许向右或向下,求一条乘积末尾0最少的路径
分析:
考虑每个数质因数分解,末尾0只会来自于2*5,因此我们只要最小化路径上2和5的数量,不难发现2和5分别独立,最优解必然选择2和5中的更优解,问题转化为包含2最少的路径,记状态为 dp[i][j] ,直接转移即可
大意:
给一个括号序列,求一个最长合法括号子串
分析:
(分类是dp不知道为什么)用一个栈搞一搞就行了。碰到左括号就入栈,右括号就弹栈顶,弹的时候更新答案
大意:
给一个小写字母串 s ( 1<=|s|<=2∗105 ),一次操作可以改变一个字母,求最少操作次数使得相邻两个字符不同,求最终的串
分析:
(分类是dp不知道为什么)显然对于一段相同的,假如说有 k 个,我们最少只需要 ⌊k2⌋ 次就能使他们相邻的都不一样。
大意:
给一个长度为 n(n≤3∗105) 的 01 序列,允许 k 次将某一个 0 变成 1 ,求最大化最长连续全 1 子序列长度之后的序列
分析:
(分类又是dp不知道为什么)注意到我们操作的本质是选一段区间里面 0 的数量不超过 k 然后把它全部变成 1 ,如果区间 [l,r] 可行,那么 [l+1,r] 必然可行, 我们用 twopointers 就行了
大意:
给出 n(n<=24) 个物品的坐标和箱子的坐标,人从箱子开始,每次最多运两个东西,必须把东西放回箱子,最后在箱子结束,求最小总移动距离。
分析:
n很小,我们直接状压,dp[S] 表示目前还没放回的物品的状态集合,并且此时人在箱子的最小移动距离, n2转 移,看起来会T,实际上不满。
大意:
给一个 n 行 m 列的 01 矩阵( 1<=n<=20,1<=m<=100000 ),一次操作可以将某一行或某一列的 01 全部反转,求经过不限次数的操作之后最少还剩几个 1
分析:
显然我们最多只会操作 n+m 次,也就是把每行每列都翻一次,将一行翻偶数次等于没翻。
首先我们有一个 O(2n∗m) 的算法,我们枚举每一行是否翻,然后再一列一列考虑,如果能使1更少就翻。
我们记 coli 表示第 i 行一开始的状态, mask 表示列的翻转状态,那么上面算法的方程就是
∑mi=1min(popcount(maskxorcoli),n−popcount(maskxorcol[i]))
注意到我们不关心每一行异或的值具体是多少,我们只关心里面 1 的个数,因此我们记 dp[k][mask] 为[1,m] 中有多少列与 mask 异或以后有恰好 k 个 1 ,那么我们要求的式子就变成了
大意:
要求给 n(n≤109) 个格子染 K 种颜色,每个格子只能染一种颜色,同一种颜色间隔不能超过 P(P≤10),要求最先P个和最后P个每种颜色恰好出现一次 ,求方案数
分析:
像这样间隔不超过一个很小的数的条件,往往启发我们对前面若干位进行状态压缩。
在本题中,对于每个格子,我们只关心它前 P 个格子的染色状况,因此我们考虑对前 P 个格子进行状压,
记录各种颜色已经从左往右染到了哪里。
如果就这样直接转移,很快我们会发现这样会导致重复计数,并且有些格子可能根本没有被染色,因此我们需要加强对转移和状态的限制,我们限制状态的最高位必须为 1 ,转移时必须从这个 1 开始转移。
大意:
将一群人分为若干组过桥,一组人的总重不能超过一个数,一组人过桥耳朵时间是这组人里面最慢的那个,最小化所有人过桥时间,人数 ≤16
分析:
人数很小,我们对每个人是否已经被分配组状态压缩,对于一个状态,我们需要枚举它的一个子集进行转移。
枚举子集可以这样方便的实现
for(int j = i; j; j = i & (j - 1))
其中 i 是全集, j 不重不漏地枚举了所有子集
可以证明枚举子集的复杂度是 O(3n)
大意:
考虑长度为 n(n<=50) 的所有01串,两个01串本质相同当且仅当它们可以通过01互变或串反转得到对方,求字典序第 k(k<=1016) 小的与小于它的所有串本质不同的串。
分析:
对于这种求字典序 K 小的合法串的问题,我们一般考虑逐位确定。
显然和一个01串本质相同的串最多只有3个,分别是串反转,01反转,串反转+01反转,而我们的答案要求的就是第k个小于和它本质相同的三个串的01串。
假设当前已经确定的前缀为 pre,我们需要计算出前缀为pre0的合法串是否满k 个。我们考虑用数位dp来进行计数。
记 dp[l][f1][f2]表示当前为第l位 ,是否小于串反转,是否小于01反转,枚举第 l和n+1−l位是0/1进 行转移,记忆化搜索实现。
大意:
求 [L,R] 以内至少出现了一次相邻的相同的三个数字,不同时出现 8和4的11位 数的数量
分析:
典型的数位dp,状态 dp[dep][last][cnt][cons][eight][four][lim]
表示第 dep 位 上一个数是 last 上一个数连续了 cnt 次 是否已经有过相邻的三个相同数字,是否出现过 8 ,是否出现过 4 ,已知前缀是否等于上界,枚举下一位数字,合法则转移,记忆化搜索实现。
大意:
令 Sum(i) 为 i 在二进制下 1 的个数 求 ∏ni=1Sum(i)
分析:
我们变换一下思路,转而求 [1,n] 以内二进制位恰好有 k 个 1 的数有几个。
那么这个问题就可以数位dp了
大意:
n个点的深度不低于h 的不同的二叉搜索树的数量 (n<=35) 键值为 [1,n]
分析:
首先我们将深度不低于h转化为深度恰好h,记dp[n][h]为n个点深度恰好为h的二叉搜索树的数量。
显然 dp[0][0]=1 ,否则 dp[i][0]=dp[0][i]=0
考虑任意一棵 n个点深度为h的 二叉搜索树,假如根上的数是 m ,则左子树有 m−1 个点,右子树有 n−m 个点,两者之间深度大的深度恰好为 h−1 ,这时就有两种情况
1. 左子树深度恰好为 h−1 ,左子树的合法数量为 dp[m−1][h−1] ,右子树深度随意,为 ∑h−1h′=0dp[n−m][h′],乘法原理即可得到这种情况的合法数量
2. 右子树深度恰好为 h−1 ,这与情况1其实差不多,不多赘述。
然后我们就可以求出 dp[n][h] 了
大意:
求一个环套树的最大点权独立集
分析:
一般来说,环套树上的问题我们总考虑选取一条边然后破环转化为一般树的问题。
记 f[i]为以i为根的子树在取i点的情况下的最大权值,g[i]为不取
取环上任意一边破环,设两边为 u,v
于是有两种情况
u不取,v任意,我们以u 为根跑一遍树形dp,取 g[u]
v不取,u任意,我们以v 为根跑一遍树形dp,取 g[v]
Tips: 一般树最大点权独立集
记 f[i] 为以 i为根 的子树在取i点的情况下的最大权值, g[i] 为不取
我们有
大意:
求一个环套树的最小支配集
分析:
一般来说,环套树上的问题我们总考虑选取一条边然后破环转化为一般树的问题。
设 f[x] 为以 x为根的子树选择x的最小 支配集, g[x]为不选择x的最小支配集
取环上任意一边破环,设这个点为 x 出边指向的点为 y
讨论
1.若 x 选择 则y一开始就是被支配状态 g[y]初值为0 求一遍最小支配集
2.若 x不选正常求最小支配集即可
Tips: 一般树最小支配集
和最大点权独立集状态类似只不过有三种状态,一是 i 在支配集中,二是 i 被至少一个儿子支配,三是 i 被父亲支配
大意:
给定一棵树,你需要把其中的 k 个点染成黑色,使得黑色点两两之间的距离和+白色点两两之间的距离和最大,求最大值
分析:
用 f[i][j] 表示在以 i 为根的子树,有 j 个黑点的最大权值。
这个权值指的是,这个子树内部的点对间距离的贡献,以及 i和fa[i] 之间的边对答案的贡献(比如这条边对黑点对距离和的贡献就是子树内部的黑点数 * 子树外部的黑点数 * 这条边的权值)。
然后 DFS来求,枚举i的每个儿子j,现在 的 f[i][]是包含了[1,j−1]子树,然后两重循环枚举范围是[1,j−1]的子树总Size和j的 Size,来更新 f[i][],这样更新之后的f[i][]就是[1,j] 子树的答案了。
大意:
给定一个 n 个点的无根树,要求找到一个根节点,使深度之和最大
分析:
对于这种无根树换根的问题,我们一般考虑做两遍dp,第一遍任意选择一个点为根dp出子树信息,从儿子推父亲,第二遍dp出某一个点为根时其它子树的信息,从父亲推儿子
在本题中,我们先把这棵树处理成以1为根的有根树,维护以每个点为根的子树的节点数 size[] ,我们逐层 O(1) 查询,设 a=fa[b] ,且已知 ans[a] ,那么从 a为根转为b为根 , b的子树中的点深度都−1, 其他点深度都 +1 ,所以得到递推式 ans[i]=ans[fa[i]]−size[i]+n−size[i]
大意:
给一棵 n(n<=200) 个点的树,求两条不相交路径使得路径总长最大
分析:
观察发现两条不相交路径必然可以通过割断树上的某条边之后分别处于割出来的两个子树里,因此我们枚举割断某条边,dp出两侧的最长路,这种问题我们也需要两遍dp,一遍父亲推儿子,一遍儿子推父亲
大意:
给定一棵树,边上有边权, m 次询问,每次选定一些关键点,求将 1 号节点与所有关键点都切断所需的最小花销,询问总点数 <=5∗105
分析:
首先我们需要用到一种新的武器——虚树
虚树是这样一棵树:在原树上选出若干关键点,将没有分叉的边全部压缩成一条边,从而得到一棵与关键点数量成线性的树
对于像该题的问题来说,本来我们应该对于每个询问都对整棵树重新做一边树形dp,但这样显然太费时。
对于每个询问,我们如果能够建出它对应的虚树,那么在虚树上dp的总复杂度就只与总询问点数有关,大大降低了时间复杂度
如何维护虚树呢?我们这样做
维护一个栈,栈中的元素形成一条由根节点出发的链,初始栈中只有根节点
将所有关键点按照 DFS 序排序
每次加入一个节点,求出节点与栈顶的 LCA ,将栈中所有深度大于 LCA 的节点全都弹掉
然后将 LCA 和该节点入栈,注意有些重复的情况要考虑
知道如何维护虚树之后,我们继续考虑dp的问题
令 f[x] 表示切断以 x 为根的子树中所有关键点的最小花销
g[x] 表示 x 是不是关键点
那么对于 x 的每个子节点 y 有 f[x]=∑min(g[y]?INF:f[y],Distance(x,y))
然后我们直接在虚树上dp就可以了。
大意:
给定一棵树,求本质不同的独立集个数对 1000000007 取模后的值
分析:
首先这里涉及到一个问题——本质不同
因此首要要解决的问题是无根树同构
我们给出如下基本知识:
1. 直径是树上最远点对之间的路径。
2. 取直径的中点称为中心。直径长度为偶数时,中心在结点上;直径长度为奇数时,中心在边上。
3. 树上的所有直径都过中心。
4. 找一条直径的方法是从任意一个点 bfs 到树上最远点 x ,然后从 x bfs 找到最远点 y x到y 的路径一定是直径。
5. 无论标号如何,树的中心位置是确定的。
通过寻找重心我们可以将无根树同构转化为有根树同构,判断有根树同构最基本的方式是最小表示法
定义 S[v] 表示以 v 为根的子树的括号序列。
c1,c2,⋅⋅⋅,ck 为 v 的 k 个儿子,按照 S[c1],S[c2],⋅⋅⋅,S[ck] 的字典序对这些儿子重新进行排序。
S[v]=‘‘(”+S[c1]+S[c2]+⋅⋅⋅+S[ck]+‘‘)”
我们规定了子树的顺序(即按字典序的顺序),所以同构的树的括号序列表示具有唯一性。
而我们发现直接使用括号序列太慢了,所以我们可以使用字符串哈希
然后我们就可以判断树的同构问题了。
考虑一个点 i ,首先我们将同构的儿子并在一起,用组合数计算出它们之间本质不同的方案数,然后对于不同构的儿子直接乘法原理即可