线性动态规划

洛谷P2285 打鼹鼠

把鼹鼠按照出现的时间排成一列,因为必须先打早些出现的鼹鼠再打晚些出现的鼹鼠,所以在打当前鼹鼠时要先确定上一个打的鼹鼠是谁,打到他时一共最多打了多少鼹鼠,这种思路就和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].

洛谷P3146 [USACO16OPEN]248

区间dp,f[i][j]表示区间 i-j 合并的最大值
可以由f[i][k],f[k+1][j]转移过来,转移条件为f[i][k]==f[k+1][j]

 

背包型

动态规划_i++动态规划_前缀和_02
/*
    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]);
}
01背包求方案数
动态规划_i++动态规划_前缀和_02
/*
    输入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[]按起点排个序,剩下的就和上面的方程很像了

 

棋盘型

洛谷P2380 狗哥采矿

我们定义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

poj3071 Football

乘法原理,设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]

洛谷P2831 愤怒的小鸟

数据范围很小,考虑状态压缩,把打死的猪的集合压缩成二进制

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最多可买多少物品。最后在算状态最大值的时候要套一个二分。

 

动规求方案数

洛谷P1310 表达式的值

注意判断分类和分步,分类加法,分步乘法。这种表达式上的问题多要用到栈,而对于括号里的计算多用递归就求解

这道题可以用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的是表达式树。

在dp中有个很著名的恒等变换(手动滑稽),就是 求最值=判断最值的可行性