提高组精英班Day4

动态规划

动态规划 (dynamic programming, DP) 是求解决策过程最优化的数学方法。
动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推的方式去解决。
如何拆分问题,设计状态和状态转移方程是动态规划的核心。

无后效性(某阶段的状态一旦确定,则此后过程的演变不再受此前各种状态及决策的影响)是一个问题可以被动规求解的关键标志之一。


一、线性 DP。

(一)01 背包问题


n 件物品放入一个容量为 W 的背包。
每件物品有一个重量 wi 和一个价值 vi。
你需要从中选出一些物品,使得这些物品的总重量小于 W 且价值
之和最大。
n ≤ 500, W ≤ 5000。

设计状态: f[i][j] 表示考虑前 i 件物品,背包容量为 j 时的最优解。
设计状态转移方程:若第 i 件物品选,则最优解为f[i - 1][j - w[i]] + v[i]。

若第 i 件物品不选,则最优解为 f[i - 1][j]。
综合起来 f[i][j] = max(f[i - 1][j]; f[i - 1][j - w[i]] + v[i](w[i] ≤ j))。
初始边界: f[0][j] = 0。
最终答案: ans = f[n][W]。
1、HDU3466 Proud Merchants
有 N 件物品。
每件物品有一个售价 Pi,但需要你此时拥有 Qi 的资金才能购买,
Pi ≤ Qi。每件商品的价值是 Vi。
你一共有 M 的资金,要求最大化购买物品的总价值。

与 01 背包类似,多了一个购买要求的限制。
参考 01 背包设计状态 f[i][j] 表示前 i 件物品,花费小于等于 j 的最
优解,转移方程
f[i][j] = max(f[i - 1][j]; f[i - 1][j - P[i]] + V[i](Q[i] ≤ j))。
先买后买有区别,不满足无后效性!
考虑两件商品 A 和 B 都被购买,先买 A 需要 PA + QB 的资金, 先
买 B 需要 QA + PB 的资金。当 PA + QB ≤ QA + PB 时,先买 A,
即 QB - PB ≤ QA - PA。
靠后的先买,按 Qi - Pi 从小到大排序。
 

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
int n,m;
struct node
{
    int p,q,v;
}a[501];
int dp[5002];
bool cmp(node a,node b)
{
    return a.q-a.p<=b.q-b.p;
}
int main()
{
    int i,j;
    freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++)
            scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].v);
        sort(a,a+n,cmp);
        for(i=0;i<n;i++)
        {
            for(j=m;j>=a[i].q;j--)
                dp[j]=max(dp[j],dp[j-a[i].p]+a[i].v);
        }
        printf("%d\n",dp[m]);
    }
}

2、crisis

一个无限大的地图上有一些士兵和一些旗子。
司令员可以下达四种口令:分别让所有士兵向东、西、南、北前进一个单位。
如果执行完一个指令后,有 K 个士兵到达了有旗子的位置,那么司令员可以得到 K 分。
问司令员在最多下达 C 个指令的情况下,最多能得到几分。
士兵数量 ≤ 1000,旗子数量 ≤ 1000,指令数量 ≤ 30。

所有士兵的相对位置不变,所以得分的情况只和一个士兵与自己的起始点的相对位置有关。
设 ai;j 表示所有士兵和自己的相对位置距离 (i; j) 时,得分是多少。
设 fi;j;k 表示司令员下达了 k 个指令,士兵离起始点的相对位置为(i; j) 时,能获得的最大分数。
则 fi;j;k = max(fi±1;j±1;k-1) + aij;

3、互质
给定一个正整数序列 a1; a2; :::; an。
你需要挑出这个序列的一个子序列,使得这个子序列的任意两个相邻元素不互质。在此基础上,你需要最大化子序列的长度。
n ≤ 1000, ai ≤ 100000。

设 fi 表示选择到第 i 个元素,并且以这个元素结尾时,最长的子序列长度。
则 fi = maxffjg + 1(如果第 i 个元素和第 j 个元素不互质)。时间复杂度 O(n2)。
n ≤ 100000?
优化的关键在于:两个数不互质,只需要它们有共同的一个质因子即可。

求出 100000 内的所有质数。
设 gi;j 表示只用前 i 个元素(这时不需要保证第 i 个元素在结尾),并且最后一个元素包含第 j 个质数。
如果 ai 不包含第 j 个质数,那么 gi;j = gi-1;j。
如果 ai 包含第 j 个质数,同时它包含第 k 个质数,那么gi;j = maxfgi-1;kg + 1。
但是,我们不可能完完全全按上面的 DP 方程做,这样做在空间上就已经不能承受了。
注意到 gi;∗ 和 gi-1;∗ 的差别是很小的,即大部分的值都是一样的。所以,我们可以考虑不把 i 这一维建出,而是直接在原数组上做修改。
在 100000 内的数最多有 6 个不同的质因数, 6 可以看作常数。因此程序的复杂度可以看作 O(n)。
4、物品分配

给出 n 个物品,每个物品有一个重量(正整数),它们的重量和为m。你可以从 n 个物品中任意选取若干个物品。你需要求出:你得到的物品的重量和有多少种不同的可能。
n ≤ 100000, m ≤ 1000000。
背包 DP 问题。
考虑用如下手法处理具有相同重量的物品:如果某个重量的物品有k 个,则可以把 k 拆成 1 + 2 + ::: + 2p + r,这样拆分前后物品的选取情况是等价的。
从小到大进行这样的拆分,则可以保证每种重量的物品个数不超过3。
又由于物品的重量和为 m,所以拆分后的物品个数是 O(pm) 级别的。
设 f 是表示最终状态的数组(即 f[i] 表示重量 i 是否可以取到),枚举每个物品,若物品重量为 c,则可以用 f = fj(f << c) 来更新 f。
复杂度 O(mpm × w),大致有 w = 32 1 。
5、补兵

有 n 个小怪,每个小怪的血量分别为 a1; a2; :::; an。你和另一名玩家正在对小怪进行攻击。每次你可以挑选一个小兵造成 1 点伤害,接着另一名玩家会对所有小兵都造成 1 点 AOE 伤害。求出你最多可以补到多少兵(有多少兵在你攻击后血量由 1 变为
0)。
n; ai ≤ 1000。

考虑一个最特殊的例子:小兵的血量分布恰好长成 1; 2; 3; :::。如果是这样,我们显然可以补到每一刀。
现在的状况是,我们面对的局面并不是这样,所以我们应该先攻击一些小兵,使得小兵的血量分布变成上面描述的情况(有一个专门的术语就描述了这种操作,叫做“垫刀”)。
还可以明确的情况是,如果有两个小兵的血量一直是一样的,那么这两个小兵我们至多只能补到一个。
所以,如果我们发现在血量 i 没有小兵,应该从血量高于 i 的小兵中,挑一个血量和别的小兵相同的、血量尽量少的来补齐。
这样,我们可以当作这个小兵的血量就是 i,只是在选择它前需要垫几刀。
具体到 DP,设 f[i][j] 表示考虑到血量为 i 的小兵,在操作的过程中省下了 j 刀的情况下所能补到的最大兵数。
如果第 i 个小兵不补,则 f[i][j] = f[i - 1][j - 1];如果要补,则f[i][j] = f[i - 1][j + c[i]] + 1c[i] 表示要补血量被当作的小兵之前要垫多少刀。两者取一个最大值即可。

二、区间 DP。

1、架设电话线
给定一个正整数序列 a1; a2; :::; an。
你需要对这个序列进行一些调整,调整的方法是对序列的某一些元素加上非负整数。不同的元素可以加不同的值。
如果你对一个元素加上了 X,那么你需要付出 X2 的代价。
在修改完序列后,如果相邻两个元素相差了 Y,你需要付出 C × Y的代价(C 是一个给定的正整数)。
你的目标是最小化付出的代价总和。
n ≤ 100000, ai ≤ 100。
设 fi;j 表示把 ai 修改为 j 的代价(j 不能比 ai 小)。则 fi;j = min((j - ai)2 + fi-1;k + C × |k - j|)。
上面的方程中, i、 j、 k 都需要枚举,总复杂度为 O(nm2)(m 为 ai的最大值)。
如果已知 k 比 j 小,那么方程可以写成fi;j = min((j - ai)2 + fi-1;k - C × k + C × j)。
这样,如果我们能预先求出 fi-1;k - C × k 部分的最小值,就不需要枚举 k 了。
设 gi;j 表示对所有不超过 j 的 k, fi-1;k - C × k 的最小值。
则 gi;j = min(gi;j-1; fi;j - C × j)。那么,原来的方程在 k ≤ j 时,就能写成
fi;j = (j - ai)2 + gi-1;j + C × j。
同样地,我们可以对 k > j 的部分做类似的处理。
这样,复杂度就可以降为 O(nm)。
2、打扫卫生
给定一个序列 a1; a2; :::; an。
你需要把这个序列切成若干段(即每一段里的元素是连续的)。
对于每一段,如果这段里有 k 个不同的数,它产生的代价是 k2。
你需要最小化代价。
n; ai ≤ 40000。
还是先考虑暴力做法:设 fi 表示到 ai 时恰好切了一段时的最小代
价,再设 gj;i 表示从 j 到 i 这一段里有多少个不同的数。
fi = min(j + gj+1;ig)。
优化的点:考虑最简单的分割方式:一个元素一段。这样的代价恰好是 n。
所以,我们枚举的 j 和 i 之间的不同元素的个数不能超过 pn。
问题仍然存在:这一条性质不方便直接应用。
挖掘“不同的数”的性质:只需要知道从 i 往前的第 1; 2; :::; pn 个出现的新的数即可。
记录 bi;j 表示从 i 往前的第 j 个出现的数的位置。
显然, bi;1 = i。
接着看 bi;2,它有很大可能是 i - 1,也就是 bi-1;1。

出问题的情况: ai-1 和 ai 一样。
类似地,我们可以把 b 从 i - 1 迁移到 i,注意判断 ai 在 bi-1;∗ 中的出现情况即可。
一个很显然的性质: fi 是单调递增的。
所以选取所有 bi;∗ + 1 作为转移点最优。
复杂度 O(n*n^1/2)。
3、多维网络

给定一个 d 维的网格图,你需要从 (1; 1; :::; 1) 走到 (a1; a2; :::; ad)。
并且你只能在网格上沿着某一维坐标增大的方向走。
在网格图中有 k 个坏点,即移动时你不能经过这些点。
你需要计算合法的路径方案数有几种,答案对一个质数取模。
ai ≤ 105; d ≤ 100; k ≤ 500
先考虑 k = 0 时的答案
可以推导得出,这时的答案应为 (∏∑(aaii!) )!。预处理出组合数及逆元,
我们可以在 O(d) 的时间内计算出解。
使用 DP 来解决 k ̸= 0 时的情况
设 fi 表示从出发点走到第 i 个坏点,且不经过其它坏点时的方案
数。如果把终点当作第 k + 1 个坏点,则答案就是 fk+1。
使用容斥原理来计算 fi:先考虑没有其它坏点时,用公式算出出发
点到 i 的方案数。再扣去经过了其它坏点时的情况。
设一条不合法的路径经过的第一个坏点是 j,则从出发点到 i 就会
有 fj × calc(j; i) 的不合法路径数。其中 calc(j; i) 表示第 j 个坏点到
第 i 个坏点任意走的方案数。
以此枚举所有 j,并把所有 fj × calc(j; i) 扣去即可。
新的问题:转移顺序?
如果坏点 j 能走到 i, i 就一定不能走到 j。按坏点坐标进行 d 个关
键字的排序即可。
复杂度 O(d × max ai + d × k2)
4、最长公共子序列 2
给定两个序列 a1; a2; :::; an,及 b1; b2; :::; bm。
你需要选出一个序列片段,使得这个这个片段在两个序列中都有出
现过(不需要连续)。
在本题中,比较特殊的地方在于: n = m,且两个序列都是一个 1到 n 的排列。
n ≤ 100000。

如果序列 a 长成 1; 2; 3; :::; n,那么我们所选取的序列在 b 应该是单
调上升的。
事实上我们并不关心每个元素分别是什么,所以可以把 a1 就看成1, a2 看成 2,……, an 看成 n。
对序列 b,做和 a 一样的变换。
例子:比如 a 和 b 原来分别为”2413” 和”1342”,变换后分别变成”1234” 和”3421”。
问题转化为:求序列 b 的最长上升子序列。
设 fi 表示到第 i 个元素为止(并且第 i 个元素入选)的最长上升子
序列长度。
fi = maxffjg + 1。要求 aj < ai。
维护一个序列 c,满足 cp = fq,其中 aq = p。
转移时,所需要做的即为求 c1; c2; :::; cai 的最大值。
使用树状数组或线段树即可完成这一步。
5、石子合并

有 N 堆石子排成一排,每堆石子有一定的数量。
现要将 N 堆石子并成为一堆。
合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过 N - 1 次合并后成为一堆。
求出总的代价最小值。

区间 DP。
dp[i][j] 来表示合并第 i 堆到第 j 堆石子的最小代价。
dp[i][j] = min(dp[i][j]; dp[i][k] + dp[k + 1][j] + w[i][j])。
w[i][j] 表示 i 到 j 的重量。
注意 DP 顺序。
6、狼

有 n 只狼排成一排,每只狼有一个攻击力 ai。
你需要按某种顺序杀死所有狼,每杀死一只狼,你会受到的伤害是这只狼左右的狼的攻击力的和。
求受到伤害的和的最小值。
n ≤ 200
区间 DP。
设 f[i][j] 表示杀死区间 [i; j] 内的所有狼,且在此过程中位置 i - 1
和 j + 1 的狼始终存活的情况下,受到的最小伤害。
枚举区间内最后杀死的狼 k,则有
f[i][j] = minf[i][k - 1] + f[k + 1][j] + a[i - 1] + a[j + 1]。
边界条件 f[i][i - 1] = 0,答案 ans = f[1][n]
n ≤ 200
7、能量项链

有 N 珠子组成一条项链,每个珠子有一个前标记和一个后标记(数
字),前一个珠子的后标记等于后一个珠子的前标记。
每次可以将两颗 (m, r),(r, n) 珠子合并成一颗珠子,产生 m ∗ r ∗ n的能量,合并后的珠子为 (m, n)。
求最后合并成一颗珠子可以释放的最大能量。
注意项链是一个环。
N ≤ 100
将序列延长一倍,破环成链。
f[i][j] = max(f[i][k] + f[k + 1][j] + e[i] ∗ e[k + 1] ∗ e[j + 1])
ans = max(f[i][i + n - 1])

三、树形 DP。

1、STA

给定一棵 n 个点的无根树。
你需要找一个点做根,使得所有点深度(即到根经过的边数)之和最大。
n ≤ 100000。
对于这种“挑一个最优的点”的题目,我们往往先找一个点,计算出相应的值,然后考虑把一个点的值转移到相邻的点上。
设 fi 表示以 i 为根的时候的答案。

先以 1 号点为根,并计算出 f1。
假设以 1 号点为根时, u 是 v 的父亲。并且 fu 的值已经被求出。
考虑根从 u 移动到 v,则 v 的子树中的点的深度都变小了 1,其余点都变大了 1。
即变化量为 -sizev + n - sizev = n - 2sizev,其中 sizev 为子树 v 内的节点个数。
所以方程为 fv = fu + n - 2sizev,根据方程进行 DP 即可。

2、最大联通块

给定一棵 n 个点的树,每个点上有点权(可能为负)。
你需要挑出树里的一个点集,使这个点集中任意两点路径上的点也在点集中(也就是一个连通块)。在此基础上,你需要使点集内的点权和最大。
n ≤ 100000。
还是挑一个点为根,用 dfs 来进行 DP。
设 fi 表示只考虑以 i 为根的子树,在点 i 一定需要被选择的情况下的最大连通块和是多少。
fi = vi + ∑ fj。其中, vi 表示 i 的点权, j 是 i 的儿子, ∑ fj 的含义是把所有 fj > 0 的部分全部加上。
对于任意一个连通块,存在唯一一个点最靠近根,则一个最优的连通块会在最靠近根的点上被计入答案。
以上两题是树 DP 最基础的两题,但它们的思想是树 DP 中最为常见的,即“将一个点的答案推广到它周围的点”以及“在离根最近的点上考虑问题”。
3、ACM2013-长沙赛区- ProblemI
给定一棵 n 个点的树, 1 号点为根。每个点上有点权 ai(可能为负)。
每个点还有一个花费 ci,表示你可以花 ci 的代价把 i 为根的子树内的所有权值取反。
求你的最大收益(权值和减去取反的花费)。
n ≤ 100000。

这道题和上面的几题不太一样的地方在于:一个点的祖先会对这个点造成影响。
所以我们需要在状态中把祖先的行为纳入考虑。
设 fi;0 和 fi;1 分别表示 i 的祖先对点 i 翻转了偶数次/奇数次的情况下,只考虑 i 为根的子树的最大和。
对于每个 DP 值,都需要分两种情况进行讨论,即 i 这个点是否要翻转。对于 fi;0 如果 i 要翻转,则 fi;0 = -ai - ci + fj;1,其中 j 为 i的所有儿子;如果不翻转,就是 fi;0 = ai + fj;0。两者取一个较大的。
再考虑 fi;1,可以类似地进行讨论。所以 fi;1 应该从 -ai + fj;1 和ai - ci + fj;0 中选取一个较大的。
最后的答案即为 f1;0。


四、数位 DP。
五、状压 DP。
六、字符串 DP。
七、单调队列,斜率优化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值