线性动态规划
把鼹鼠按照出现的时间排成一列,因为必须先打早些出现的鼹鼠再打晚些出现的鼹鼠,所以在打当前鼹鼠时要先确定上一个打的鼹鼠是谁,打到他时一共最多打了多少鼹鼠,这种思路就和LIS很像了,一点不同就在于并不是所有t[i]<t[j]都可以先打i再打j,必须满足两鼹鼠的曼哈顿距离小于等于他们的出现时间差
洛谷P1868 饥饿的奶牛
a[i][0]表示i位置是否为某一区间的起点
a[i][1]表示以i为起点的区间的价值
f[i]表示到i位置的最大价值是多少
因为不允许区间重叠,所以一旦选了某个区间,其他的价值只能来自这个区间之外,考虑从前往后进行状态转移,那么f[i+a[i][1]]=max{f[i]+a[i][1]} 其中i必须保证是区间i的起点
P3004 [USACO10DEC]宝箱Treasure Chest
区间dp
f[i][j]表示在i,j这段区间内先手能获得的最大分数;那么后手在先手最优方案走法下,按最优方案走的最大分数就是i,j这个区间总分数减去f[i][j].
区间dp,f[i][j]表示区间 i-j 合并的最大值
可以由f[i][k],f[k+1][j]转移过来,转移条件为f[i][k]==f[k+1][j]
背包型
/* for(int i=1;i<=n;i++) for(int j=m;j>=a[i];j--) f[j]+=f[j-a[i]]; 这样吗 为什么这样m元钱就是刚好花完的 从m开始枚举的 枚举了所有的状态 所以从1到m都会有 如果m花不完或者凑不出m 那么m的前继状态的方案数一定是0 无论怎样f[m]+=f[m-w[i]] f[m]都是0 但是如果可以拼出 刚才的枚举就会把方案数转移出 因为可以从n层枚举摘取这样的情况 f[0]=1,w[1]=1,w[2]=2,m=3 f[1]+=f[1-w[1]] f[3]+=f[1-w[2]] 那么f[m]就能从f[1]转移过来 f[1]就能从f[0]转移过来 */ #include<iostream> #include<cstdio> using namespace std; int f[10000],n,m,a[110]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&a[i]); f[0]=1; for(int i=1;i<=n;i++) for(int j=m;j>=a[i];j--) f[j]+=f[j-a[i]]; printf("%d",f[m]); }
/* 输入x,y,z表示第i个物品的重量为x,价值为y,属于z组 */ #include<iostream> #include<cstdio> #define maxn 1010 using namespace std; int n,m,f[maxn],a[maxn][maxn],v[maxn],w[maxn],t; int main(){ scanf("%d%d",&m,&n); int x,y,z; for(int i=1;i<=n;i++){ scanf("%d%d%d",&w[i],&v[i],&z); a[z][++a[z][0]]=i;t=max(t,z); } for(int k=1;k<=t;k++) for(int j=m;j>=0;j--) for(int i=1;i<=a[k][0];i++){ if(j-w[a[k][i]]>=0) f[j]=max(f[j],f[j-w[a[k][i]]]+v[a[k][i]]); } printf("%d",f[m]); }
洛谷P2854 [USACO06DEC]牛的过山车Cow Roller Coaster
看到本题不难想到二维费用的背包f[i][j]=max{f[i-a[i].len][j-a[i].w]+a[i].v},但是题目要求所有铁轨首尾相连,所以需要对a[]按起点排个序,剩下的就和上面的方程很像了
棋盘型
我们定义f[i][j]f[i][j]为在以(i,j)(i,j)为右下角的子矩阵中的最大采矿量,由题意我们可知,如果(i,j)(i,j)是向左转移矿,那么(i,j-1)(i,j?1),一定也是向左,(i,j-2)(i,j?2)一直到(i,1)(i,1)都是向左,同理如果(i,j)(i,j)是向上转移矿,那么(i-1,j)(i?1,j),一定也是向上,(i-2,j)(i?2,j)一直到(1,j)(1,j)都是向左。这就可以其实我们用前缀和去维护一段区间的采矿量。
在转移时,我们只关心当前(i,j)(i,j)的采矿方向。设A[i][j]A[i][j]为向上的前缀和,B[i][j]B[i][j]为向左的前缀和,那么转移方程f[i][j]=max(f[i-1][j]+B[i][j],f[[i][j-1]+A[i][j])f[i][j]=max(f[i?1][j]+B[i][j],f[[i][j?1]+A[i][j]).
像这种要连续选择的问题可以考虑用前缀和维护
概率dp
乘法原理,设f[j][i]表示第j支球队通过第i场比赛的概率,则:f[j][i]=sum(f[j][i-1]*f[j+k][i-1]*p[j][j+k]),其中j+k是它这一场可能面对的对手,实际上就是它上一场比赛的第一支队伍加2^(i-1)一直加到2^1
数位dp
状压dp
洛谷P3070 [USACO13JAN]岛游记Island Travels
综合来说这真的是一个好题,但是dp部分还是比较基础的
因为题目要求以连通块为单位,所以就先求出所有'X'所在的连通块,flag[i][j]就是i,j位置上的点所在的联通块,num[i]是编号为i的连通块的大小,然后以每个连通块为起点进行spfa,经过所有spfa后可以得出任意两连通块之间的最短路。就可以开始dp啦!
dp[S][j]表示走过点集S到达j的最短路径,转移:dp[S/j][k] + dis[k,j]
数据范围很小,考虑状态压缩,把打死的猪的集合压缩成二进制
g[i][j]:打死i,j两头猪的抛物线能打到的猪的集合 f[s]:被打死的猪的集合为s时最少打多少次 for(int i=1;i<(1<<n);i++){ int j=0; while((i&(1<<j))==0)j++; f[i]=f[i^(1<<j)]+1; for(int k=0;k<n;k++) if(((1<<k)&i)&&k!=j) f[i]=min(f[i],f[(i&g[j][k])^i]+1); }
洛谷P3052 [USACO12MAR]摩天大楼里的奶牛Cows in a Skyscraper
f 数组为结构体
f[S].cnt 表示集合 S 最少的分组数
f[S].v 表示集合 S 最少分组数下当前组所用的最少容量
f[S] = min(f[S], f[S - i] + a[i]) (i ∈ S)
运算重载一下即可。
洛谷P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows
比较简单的一个题
f[sta][i]表示选了的牛的集合为sta,且最后一个牛是i的混乱序列方案数,正着推就行
if(sta&(1<<(i-1))) for(int j=1;j<=n;j++) if(abs(a[j]-a[i])>k)f[sta][i]+=f[sta^(1<<(i-1))][j];
洛谷P3092 [USACO13NOV]没有找零No Change
因为是顺序购买,所以状态s的答案有关的是使用顺序。之后就有状态f[s]表示用状态s最多可买多少物品。最后在算状态最大值的时候要套一个二分。
动规求方案数
注意判断分类和分步,分类加法,分步乘法。这种表达式上的问题多要用到栈,而对于括号里的计算多用递归就求解
这道题可以用DP求解,设f(s,0)为s=0的方案数,f(s,1)为s为1的方案数,则
f(a+b,0)=f(a,0)*f(b,0);
f(a+b,1)=f(a,0)*f(b,1)+f(a,1)*f(b,0)+f(a,1)*f(b,1);
f(a*b,0)=f(a,0)*f(b,0)+f(a,1)*f(b,0)+f(a,0)*f(b,1);
f(a*b,1)=f(a,1)*f(b,1)。
接下来就是一个类似于树形DP的过程了。在这里DP的是表达式树。