图论之图的存储
直接存边
方法
使用一个数组来存边,数组中的每个元素都包含一条边的起点与终点(带边权的图还包含边权)。(或者使用多个数组分别存起点,终点和边权。)
代码实现
1234567891011121314151617181920212223242526272829struct Edge { int u, v;};const int N = 1e5;int n, m;Edge arr[N];bool vis[N];bool find_edge(int u, int v) { for (int i = 1; i <= m; i++) { if (arr[i].u == u && arr[i].v == v) return true; } return false;}void dfs(int u) { if (vis[u]) return; vis[u] = true; for (int i = 1; i <= m; i++) { ...
动态规划之数位dp
引入
数位是指把一个数字按照个、十、百、千等等一位一位地拆开,关注它每一位上的数字。如果拆的是十进制数,那么每一位数字都是 0~9,其他进制可类比十进制。
数位 DP:用来解决一类特定问题,这种问题比较好辨认,一般具有这几个特征:
要求统计满足一定条件的数的数量(即,最终目的为计数);
这些条件经过转化后可以使用「数位」的思想去理解和判断;
输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;
上界很大(比如 10^18),暴力枚举验证会超时。
数位 DP 的基本原理
考虑人类计数的方式,最朴素的计数就是从小到大开始依次加一。但我们发现对于位数比较多的数,这样的过程中有许多重复的部分。例如,从 7000 数到 7999、从 8000 数到 8999、和从 9000 数到 9999 的过程非常相似,它们都是后三位从 000 变到 999,不一样的地方只有千位这一位,所以我们可以把这些过程归并起来,将这些过程中产生的计数答案也都存在一个通用的数组里。此数组根据题目具体要求设置状态,用递推或 DP 的方式进行状态转移。
数位 DP 中通常会利用常规计数问题技巧,比如把一个区间内的 ...
动态规划之状压dp
定义
状压 DP 是动态规划的一种,通过将状态压缩为整数来达到优化转移的目的。
引入
题目描述
互不侵犯
在 N*N 的棋盘里面放 K 个国王(1 <= N <= 9, 1 <= K <= N * N),使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 个格子。
思路分析
设 dp(i,j,l) 表示前 i 行,第 i 行的状态为 j,且棋盘上已经放置 l 个国王时的合法方案数。
对于编号为 j 的状态,我们用二进制整数 state(j) 表示国王的放置情况,state(j) 的某个二进制位为 0 表示对应位置不放国王,为 1 表示在对应位置上放置国王;用 stateCount(j) 表示该状态的国王个数,即二进制数 state(j) 中 1 的个数。例如,如下图所示的状态可用二进制数 100101 来表示(棋盘左边对应二进制低位),则有 state(j)=100101, stateCount(j)=3。
设当前行的状态为 j,上一行的状态为 k,在保证当前行和上一行不冲突的前提下,枚举所有可 ...
动态规划之树形dp
概括
树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形 DP 一般都是递归进行的。
基础
没有上司的舞会
某大学有 n 个职员,编号为 1 --- N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 Ai,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
思路分析
我们设 f(i,0/1) 代表以 i 为根的子树的最优解(第二维的值为 0 代表 i 不参加舞会的情况,1 代表 i 参加舞会的情况)。
对于每个状态,都存在两种决策(其中下面的 x 都是 i 的儿子):
上司不参加舞会时,下属可以参加,也可以不参加,此时有 f(i,0) = sum{max{f(x,1),f(x,0)}}。
上司参加舞会时,下属都不会参加,此时有 f(i,1) = sum{f(x,0)} + Ai。
我们可以通过 DFS, ...
动态规划之区间dp
定义
区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。
令状态 f(i,j) 表示将下标位置 i 到 j 的所有元素合并能获得的价值的最大值,那么
f(i,j)=max{f(i,k)+f(k+1,j)+cost}f(i,j)=\max\{f(i,k)+f(k+1,j)+cost\}
f(i,j)=max{f(i,k)+f(k+1,j)+cost}
cost 为将这两组元素合并起来的代价。
性质
区间 DP 有以下特点:
合并:即将两个或多个部分进行整合,当然也可以反过来;
特征:能将问题分解为能两两合并的形式;
求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。
典型例题
石子合并1
在一个操场上摆放着一排 N 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的 2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将 N 堆石子合并成一堆的最小得分。
1234567891011121314151617 ...
动态规划之线性dp
概述
线性动态规划,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板。
线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值。
因此,除了少量问题(如LIS、LCS、LCIS等)有固定的模板外,大部分都要根据实际问题来推导得出答案。
典型题目
LIS
线性dp
12345678910class Solution {public: int lengthOfLIS(vector<int>& nums) { vector<int> dp(nums.size(), 1); for (int i = 1; i < dp.size(); i++) for (int j = 0; j < i; j++) if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); return ...
动态规划之背包问题
01背包
01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi ,价值是 wi 。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
1234567891011121314int n;int v;cin >> n >> v;vector<int> arrV(n);vector<int> arrW(n);for (int i = 0; i < n; i++) { cin >> arrV[i]; cin >> arrW[i];}vector<int> dp(v + 1, 0);for (int i = 1; i <= n; i++) for (int j = v; j >= arrV[i - 1]; j--) dp[j] = max(dp[j], arrW[i - 1] + dp[j - arrV[i - 1]]);cout << dp.back( ...
动态规划之记忆化搜索
定义
记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。
因为记忆化搜索确保了每个状态只访问一次,它也是一种常见的动态规划实现方式。
引入
采药
题目描述
山洞里有 M 株不同的草药,采每一株都需要一些时间 t_i,每一株也有它自身的价值 v_i。给你一段时间 T,在这段时间里,你可以采到一些草药。让采到的草药的总价值最大。
暴力DFS做法
很容易实现这样一个朴素的搜索做法:在搜索时记录下当前准备选第几个物品、剩余的时间是多少、已经获得的价值是多少这三个参数,然后枚举当前物品是否被选,转移到相应的状态。
123456789101112131415161718192021222324252627282930struct node { int time; int value; node() = default; node(int time, int value) : time(time), value(value) {}};node arr[105];int T, M;int ans = ...