该博客会一直更新优秀dp的状态
1.求两个字符串相同子序列的个数:
设dp[i][k] 表示表示字符串s前i个字符与字符串t前k个字符相同的子序列个数
那么
dp[i][k]=dp[i-1][k]+dp[i][k-1]-dp[i-1][k-1]//类似于容斥
如果s[i]==t[k] dp[i][k]+=dp[i-1][k-1]+1 //新增子序列+自己
2.二进制给出L,求满足a+b<=L&&a^b=a+b的(a,b)有多少个
首先,a^b=a+b 可以推出 a与b的二进制任何一位都不可以同时为1
对于每一位来言,ab在这一位上的组合有3种 01 10 00
我们设dp[i][1]代表到第i位,a+b=L且a+b=a^b的对数
设dp[i][0]代表到第i位,a+b<L且a+b=a^b的对数
那么如果L的第i位为1 那么此时这一位可以取三种情况00 01 10
并且由于权值从大到小枚举,所以加上1,也不会使得之前小于L的数量改变(因为小于L 绝对是小于了一个2^k)
所以dp[i][0]=dp[i-1][0]*3
如果当前 L第i位 为1 :
dp[i][1]=dp[i-1][1]*2 //取(01 10)使得之前等于L 现在也等于L
dp[i][0]+=dp[i-1][1] // 这一位如果不取可以使之前等于L的变的小于L
如果当前 L第i位 为0 不做修改
3.将一段递增的区间划分为若干段,要求每段长度必须大于k,求如何划分使得 每段的极差和最小
划分dp的题目,比赛中由没有推出来,赛后自己推出来了,记录一下。
设f[i]表示前i个元素已经满足要求的最小花费
首先,区间长度必须要大于k,所以起始取值为k,又发现在k到2*k-1之间,只能划分为一段,否则前面的一段将长度<k
并且 k~2*k-1之间,f[i]的值为 num[i]-num[1]
对于大于>=2*k的位置,f[i]=min(f[j-1]+num[i]-num[j],f[i])//确保j-1段满足要求,使得[j,i]段取值成为新的一段序列,可得出j的取值范围 (k<=j<=i-k+1)
对转移方程 :f[i]=min(f[j-1]+num[i]-num[j],f[i])进行转换:f[i]=min((f[j-1]-num[j])+num[i],f[i])
所以我们只需要维护一个f[j-1]-num[j]的前缀最小值即可,O(1)转移
4.树形dp:在树上的第i个节点放置一个守卫需要花费val[i],放置完成后,相邻的所有点都被监视到,问如何在树上放置点,使得花费最小并且监视到所有点?
很不错的树形dp,状态比较难想 ,搞清楚状态之后其实这题不难
对于dp[i][3],令:
dp[i][0]为该位置不放节点,该节点由父节点监视。
dp[i][1]为该位置不放节点,该节点由子节点监视。
dp[i][2]为该位置放节点
那么很容易得到:
dp[i][0]:因为该位置不放节点,由父节点监视,所以子节点有两种状态:第一种放置守卫,第二种不放守卫继续由子节点的子节点监视。
dp[i][1]:因为该位置不放节点,由子节点监视,所以需要有一个子节点进行监视,然后其余的子节点两种状态,不放-由子节点监视(这时不可以由父节点监视)或者放。进行状态转移时,我们首先求和所有子节点的 sum+=min(dp[e][2],dp[e][1]),所以我们枚举一下放哪一个子节点就好了,枚举第k个节点监视该节点产生的最小花费为 sum-min(dp[k][1],dp[k][2])+d[k][2]
dp[i][2]:该位置放,那么对于子节点的状态而言,没有什么限制,可以加三种状态的最小值。
5.有n个数字,问从n个数字中取出若干个数字,他们的和modp为m,问有多少种取法?
这题是基础计数dp,很容易想到对于每一个数字来讨论,dp[k]代表mod p为k的取法有多少种,剩下的无法文字解释,然后这篇文章的第一个代码就出现了:
dp[num[1]%p]=1;
for(int i=2;i<=n;i++){
for(int k=0;k<p;k++) cop[k]=dp[k];
for(int k=0;k<p;k++)
cop[k]=dp[k]+dp[(k-num[i]%p+p)%p];
for(int k=0;k<p;k++)
dp[k]=cop[k];
dp[num[i]%p]++;
}
printf("%lld\n",m==0?dp[m]+1:dp[m]);
转移显然成立,每一位的贡献都计算在内了。
6.给定一个集合A,集合A的真子集共有个,定义每个集合的权值是集合内所有数之和,问真子集权值排序之后中位数是多少?
题目要求 :N<=2000,a[i]<=2000
首先考虑如何与dp靠边,假设我选择一个集合权值为S,那么他的补集就是 SUM-S(SUM为全集的权值)
所以说我们将空集考虑进去,那么 所有的权值都是关于S/2对称的,也就是说中位数必须是(SUM+1)/2到SUM的其中一个。
那么dp的思路也就来,设dp[i][j]代表前i个数是否可以组成数j:
状态转移方程即为 :
考虑到第二维的状态过于庞大所以采用bitset优化为一维设f[i]代表数字i可以表示那么,对于新输入的数字x,则有f|=f<<x
最后统计(SUM+1)/2~SUM间第一个可以表示的数即可
bitset转义复杂度是普通复杂度的64倍,即该复杂度为:~=7e7
7.天平秤重:给定一些物品既可以放在左边也可以放在右边,问是否可以称出重为w的物品?
背包问题:
假设当前k可以称出,那么abs(k-x)与(k+x)都可以称出,那么状态转移方程很显然了。
不过此时只能用二维了,因为与k-x与k+x都有关。
所以二维转移
空间复杂度:O(N*M) M为所有物品最大总重量
时间复杂度:O(N*M) M为所有物品最大总重量
8.数位dp:1~n中有多少个数可以被自己数位的和整除?
显然成立问题:
首先考虑,模数一共有9*12种,分开枚举即可
dp[pos][sum][remain]表示到达第pos位时,sum为数位和,remain为前面所表示的数对当前模数取余剩余多少与当前sum加和为多少,确定两个状态即可加上满子树的值
9.一个长度为n的字符串,有多少个本质不同的长度为k的子序列?
好题!
考虑对于第i位而言,他可以作为长度为k的子序列中的第[1,k]位
所以由此可以推出dp[i][k]表示以str[i]结尾的长度为k的字符串的数量
很容易得到方程:
但是这样计算的子序列中显然存在重复,并且时间复杂度也不允许。
所以考虑什么情况下不重复:
考虑当前str[i]作为第j位,那么第j-1位置上的字母一共有26种选择,所以此时状态方案数只需要加上这26种即可,但是前面有很多相同字母,该加哪个字母的方案数呢?
例如:abbd 以d结尾的方案数应该加第二个b还是第一个b,答案显然第二个b。
所以状态转移就很清楚了:
所以可以维护一个数组f[j]{j>=1&&j<=26},表示在i之前离i最近的j+'a'的值是多少。
之后,dp[i][k]+=f[j]——{1<=j<=26}
以上是第一种解法:
考虑第二种解法:
原理不变,第i个位置长度为k的方案数只与离他最近的有关系——由此可以用序列自动机优化一下:
因为只要出现两个相同的字母,前一个字母已经把后一个字母的方案数给隔断了,所以显然没有重复
///bacc -> ba bc ac cc
总结:子序列问题掺杂dp可以与序列自动机结合一下