DP

DP

解题步骤

理论部分

1 设计\(dp\)状态

2 求状态转移方程

3 初始化必要的简单状态

复杂度分析 : 状态数量*每个状态的转移次数复杂度 或 所有转移复杂度之和

走楼梯

题目

\(设计状态\) $dp_i $: 走到第i阶的方案数

\(根据状态求出状态转移方程\) \(dp_i = dp_{i-1}+dp_{i-2}\)

\(初始化:dp_0 = 1\)

走k层

每一步可以走\(1,2,..k\)层,求方案数

\(1<n*k\leq 10^5\)

状态和上面一样设计。

转移方程为\(dp_i = \sum_{j=1}^{k}{dp_{i-j}}\)

时间复杂度\(O(nk)\)

数学优化

\(dp_{i} = dp_{i-1}+dp_{i-2}+dp_{i-3}+...+dp_{i-k}\)

\(dp_{i-1} = dp_{i-2}+dp_{i-3}+dp_{i-4}+...+dp_{i-k-1}\)

相减得 \(dp_i=2*dp_{i-1}-dp_{i-k-1}\)

时间复杂度\(O(n)\)

PS: 一些简单的递推式 可以用 矩阵快速幂优化 复杂度与\(logn\)相关 矩阵加速

一些\(dp\)求出来的递推式还可以用高中数列学过的特征根方程解出来得到\(O(1)的式子\)

\(eg\). 长度为n的环,给每一个珠子染色,可以染m中颜色,需要保证相邻珠子不同颜色,求方案数. \(n\leq10^9,m\leq10^9\)

可以\(dp\)+矩阵加速。可以推\(O(1)式子\)

前缀和优化

\(pre_i = \sum_{j=1}^{j=i}{dp_j}\)

\(dp_i = pre_{i}-pre_{i-k-1}\)

时间复杂度 \(O(n)\)

最长公共子序列

\(状态:f_{i,j} : 串1中以i结尾,和串2中以j结尾的最长公共子序列\)

转移方程

\[f_{i,j} = \begin{cases} f_{i-1,j-1}+1,s_{i}=t_{j} \\max(f_{i-1,j},f_{i,j-1}) s_i \not=t_j \end{cases} \]

初始化\(f_{i,j}=0\)

最长上升子序列

状态: \(dp_{i}:\)以i结尾的最长上升子序列

\(转移方程 :dp_i = max_{j=1,a_{j}<a_{i}}^{i-1}dp_{j}+1\)

初始化:\(dp_{i} = 1\)

复杂度\(O(n^2)\)

    rep(i,n)
    {
        cin>>a[i];
    }
    int maxn;
    for(int i=1;i<=n;i++)
    {
        dp[i]=1;
        for(int j=1;j<=i;j++) {
            if (a[j] < a[i])
                dp[i] = max(dp[i],dp[j] + 1);
            maxn=max(maxn,dp[i]);
        }
    }
    cout<<maxn;

优化

贪心算法

    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    memset(dp,0x1f,sizeof(dp));
    int mx=dp[0];
    for(int i=0;i<n;i++)
    {
        *upper_bound(dp,dp+n,a[i])=a[i];
    }
    int ans=0;
    while(dp[ans]!=mx)
        ans++;
    cout<<ans;

数据结构优化

\(转移方程 :dp_i = max_{j=1,a_{j}<a_{i}}^{i-1}dp_{j}+1\) 中 $ max_{j=1,a_{j}<a_{i}}^{i-1}dp_{j}+1 ,可以用线段树或者树状数组求一个前缀最大值,并支持单点修改$

时间复杂度\(O(nlogn)\)

思维题

P1020 [NOIP1999 普及组] 导弹拦截

P1091 [NOIP2004 提高组] 合唱队形

记忆化搜索

把所有状态都看成一个点,转移关系用有向边连接,若得到一个DAG图则可以用dp记忆化搜索

题目

[USACO1.5] [IOI1994]数字三角形 Number Triangles

最大食物链计数

SHOI2002] 滑雪

SG函数

SG函数理论

栗酱的异或和

取石子

背包DP

理论

  w  v
1 3  2
2 4  2
3 7  4

int dp[i][j];

dp[1][6]=2;
i =2 j=6;
dp[i][j] = max{dp[i-1][j], dp[i-1][j-w[i]]+v[i]};

dp[n][W];


dp[2][6] = max{dp[1][6], dp[1][6-4]+2}

0-1 背包

P1048 [NOIP2005 普及组] 采药

完全背包

P1164 小A点菜

多重背包

P1077 [NOIP2012 普及组] 摆花

区间DP

P1880 [NOI1995] 石子合并

状态:\(dp_{i,j}合并区间[i,j]的最小值\)

转移:\(dp_{i,j} = min_{i\leq k<j}~~~{dp_{i,k}+dp_{k+1,j}+sum_{i,j}},({sum_{i,j}区间[i,j]的石子数量之和)}\)

初始化\(dp_{i,i}=a_{i}\)

复杂度\(O(n^3)\)

问题:怎么设计递推顺序求出所有\(dp_{i,j}\)

问题:记忆化搜索如何设计?

P1063 [NOIP2006 提高组] 能量项链

P4170 [CQOI2007] 涂色

P1140 相似基因

博弈+dp

树形DP

P1352 没有上司的舞会

设计状态:$dp_{u,0/1} :对于以u为根的子树,选或则不选(0/1)u结点的最大值 $

转移方程\(dp_{u,0} = \sum_{v\in~son_{u}}{max{(dp_{u,0},dp_{u,1})}},dp_{u,1} = w_{u}+\sum_{v\in~son_{u}}{dp_{v,0}}\)

\(O(n)\)

P1122 最大子树和

树上背包

前置

滚动数组

举个例子求斐波拉数列

f[N]
f[1]=f[2]=1
for(int i=3;i<=n;i++){
	f[i] = f[i-1]+f[i-2]
}

空间复杂度\(O(n)\)

优化:

int f1 = 1,f2 = 1
int f  = 0
for(int i=3;i<=n;i++){
	f = f1+f2;
	f1 = f2;
	f2 = f3;
}

空间复杂度\(O(1)\)

01背包优化

for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
        f[i][j] = f[i-1][j];
        if(j>=v)f[i][j] = std::max(f[i][j],f[i-1][j-v]+w);
    }
}

空间复杂度\(O(nv)\)

优化

int f[V];
for(int i=1;i<=n;i++){
    for(int j=m;j>=v;j--){
        f[j] = std::max(f[j],f[j-v]+w);
    }
}

完全背包优化

优化

int f[v];
for(int i=1;i<=n;i++){
    for(int j=v;j<=m;j++){
        f[j] = std::max(f[j],f[j-v]+w);
    }
}

空间复杂度\(O(V)\)

分组背包

即有n个组,每个组最多只能选其中一个

多重背包可以看成一个分组背包即:

例如(c,v,w) 数量为c个,体积为v,价值为w,那么第i组的物品为: \((v,w),(2*v, 2*w),...(c*v,c*w)\)

所以多重背包可以看成等差数列版的分组背包,这个性质就能用在单调队列的优化

多重背包搞明白了,分组背包也差不多啦

P1757 通天之分组背包

题目

P2014 [CTSC1997] 选课

状压DP

SCOI2005」互不侵犯

吃奶酪

posted @ 2025-07-07 15:39  星空丶star  阅读(13)  评论(0)    收藏  举报