农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
发现数据范围很小,可以状压,那么要压什么东西?
在一个格子种了草之后其他的格子都不能种草
用f[i][s]代表前i行的草种完了,且第i行种的草的长相为d的方案数有多少
数位dp:
给定一个x,有多少个y满足0<=y<x
一位一位给每一个格子填数
从高向低填,这样方便比较大小
状态:f[i][j]表示已经从高位向低位填了i位,j=0或1,如果j=0,表示填的数已经<x,如果j=1,表示目前填的所有数都=x
初始化:f[l+1][1]=1 因为第l位之前都只能填0
转移:枚举下一位填什么
考虑自己更新别人
如果填进去数之后会导致y比x大,显然就不行了,直接不用转移
如果填进去之前j=0,那么之后不管怎么填j都只能=0
如果填进去之前j=1,只有当填的这一位和x的这一位一样的时候,j才会继续是1
f[i-1][j’]+=f[i][j]
例:求[l,r]中的数位之和
0~r的数位之和减0~l-1的数位之和
f[i][j]表示数有多少个
g[i][j]表示数字之和是多少
转移:考虑第下一位到底填什么
如果填1的话对答案的贡献是多少?由于上一位填的可能有很多种,所以1做出的贡献应该是f[i][j]*k
#include<iostream> using namespace std; int solve(int x) { int l=0; while (x>0) { l++; z[l] = x%10; x/=10; } memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); f[l+1][1]=1; g[l+1][1]=0; for (int i=l+1;i>=2;i--) for (int j=0;j<=1;j++) for (int k=0;k<=9;k++) { if (j==1 && k>z[i-1]) continue; int j_; if (j==0) j_=0; else if (k==z[i-1]) j_=1; else j_=0; f[i-1][j_] += f[i][j]; g[i-1][j_] += f[i][j] * k + g[i][j]; } return g[1][0] + g[1][1]; } int main() { cin >> l >> r; cout << solve(r) - solve(l-1) << endl; return 0; }
例:求在[l,r]中满足十进制位中相邻两个数字之差至少为2的数有多少个
f[i][j][k]表示已经填到第i位,前面是小于还是等于,第i位填的k
在填第i-1位的时候看看第i位填的是多少
windy数
例:求在[l,r]中有多少数满足各位数乘积=k
如果按照上个题的方法做,设f[i][j][k],会炸空间
因为k是很多个个位数乘起来的,所以不能出现超过10的质因子,也就是说我们开的大部分数组都是空的
设f[i][j][a][b][c][d],a,b,c,d表示2^a,3^b,5^c,7^d,a,b,c,d分别取log2 10^18 ,log3 10^18 ,log5 10^18 ,log3 10^18
但是直接做时间复杂度还是会炸,继续优化
我们发现这些如果一个数取到上界,其他就取不到上界,枚举就浪费时间了。所以直接dfs预处理出longlong内所有满足这个性质的数,直接把数组改成f[i][j][k],k表示是第k个
树形dp
顾名思义,在树上进行dp
例:求树上有多少个点(什么沙雕题)
基础思路:从下往上推,在每一个点维护他的子树的信息
f[i]:以i为根的子树有多少点,f[i]就是它所有子树的大小之和+1
例:求树的直径(在树上找到两个点使得这两个点的距离最远)
两个点之间的距离一定是一个点到他们的lca再到另一个点
可以看成是lca向下的两条路
也就是说有一个拐点。我们可以维护从每个点向下走最长和次长能走多少
用f[i][0]表示从i向下的最长路,f[i][1]表示次长路
然后枚举每一个点,对f[i][0]+f[i][1]取最大值
那么怎么算?
f[i][0]=max(f[p[j]][0])+1
f[i][1]呢?
如果我们把最长路和次长路混在一起取次长,就有可能经过同一个节点,这样就并不是一条正常的路径了
所以我们再选次长的时候,一定要避免和f[i][0]选到同一个位置去
所以我么直接把剩下的所有儿子的最长取出来取最长
#include<iostream> using namespace std; void dfs(int i) { for (p is i's son) dfs(p); for (p is i's son) { int v = f[p][0]+1; if (v>f[i][0]) { f[i][1]=f[i][0]; f[i][0]=v; } else if (v>f[i][1]) f[i][1]=v; } } int main() { cin >> n; du ru he jian shu; dfs(1); return 0; }
例:求树上路径的总长度
f[i]表示以i为根的子树有多少个点,考虑每个边被算进答案多少次
2*f[i]*(n-f[i])对答案的贡献
答案就是对边求一个Σ就完了
例:没有上司的舞会
设f[i][0/1]代表以i为根的子树里面选择一些点,0代表没选,1代表选了,在这种情况下获得的最大价值
答案就是max(f[1][0],f[1][1])
如果i选了,他的所有儿子都不能选,f[i][1]=Σf[j][0](j∈son[i]) +a[i]
如果i没选,他的儿子可以选也可以不选,f[i][0]=Σmax(f[j][0],f[j][1])(j∈son[i])
例:Strategic game
和上个题差不多
如果能守护距离不超过2的点?
f[i][0/1/2]表示在i的子树被完全覆盖的情况下往下走到第一个士兵距离是0/1/2
f[i][0]=Σmin(f[j][0],f[j][1],f[j][2])+1(j∈son[i])
g[j][0/1]表示我已经确定了前j个儿子的取值,其中这j个儿子有没有拿出一个0(有士兵)作为答案
dp套dp