POJ3311 Hie with the Pie
题面大秦 为你打开 题目传送门
题目翻译:有 $n$ 个地方(标号 $1$ 到 $n$ )要从标号为 $0$ 的地方出去,经过所有的地方之后回来,求最短的时间,输入 $(n+1)$ 的矩阵 $a$,$a[i][j]$表示顶点 $i$ 到顶点 $j$ 所需要的时间。
第一行输入一个整数 $n(1≤n≤10)$ 。
接下来 $𝑛+1$ 行,每行 $n+1$ 个整数,表示矩阵中的元素,矩阵中的元素在区间 $[1,1000]$ 内。
思路这道题我们要同时兼顾经过了哪些点(因为要经过所有点),所以最好的方式就是把经过的点存下来,但是想要在递推过程中记录这些东西,有点困难。发现数据范围只有 $10$ 不难想到状压DP。因为状压DP是基于集合的DP,每经过一个点,就在状态中标记。
另外要处理某两个点之间的最短路,因为只有 $10$ 的数据范围,所以这个可以用 floyd 水水过。
此外,在标记每一个点的是否经过,还有一个很重要的元素是最后一个点是哪里。就比如说:
121 4 3 2 1 2 3 4
同样是经过了点 1 2 3 4 但是顺序不一样,路程就可能有变化,另外我们要从最后一个经过的点往 ...
状压DP
概念:引入状压在我们做题的过程中,有很多选或不选的题目,因为递归不好优化代码也长,我们可以使用状态压缩来解决这个问题。
状压状压,是状态压缩的缩写,向上面举的例子,状态就是选或不选,想要把 $n$ 个物品的状态压缩。能想到的就是二进制吧,我们把第 $i$ 个物品的状态放在第 $i$ 位。当然,状压可以不是二进制状态。
运用:熟悉状压比如,我现在要枚举什么什么数量最大的组合,那么使用状压可以写出如下代码。
123456789for(int i = 0; i < (1 << n); i++) { int tot = 0; for(int j = 0; j < n; j++) { if((1 << j) & i) { tot += a[j]; } } ans = max(ans, tot);}
复杂度 $O(n\times2^n)$ 这就是状压的最基本运用。
拓展:状态压缩DP某一些 DP 的数据范围比较小,大概是 ...
517七段第五课I
题面大秦 为你打开 题目传送门
看不了的就看下面罢
从一个 $n\times n$ 的矩阵中选出一个元素之和最大的非空子矩阵。
思路思考一下,我们发现学过和这个最最有关的知识点应该是 “最大子段和” 。那么对于这道题的最大子矩阵应该怎么办呢?我们可以先参考一下最大子段和的代码。其主要思想就是能选就选,选不了就重开。
那么现在的问题是是否可以把最大子段和的思路借鉴到最大子矩阵中。矩阵本质上是一堆子段叠在一起得出的,且这些子段长度相等,一一对齐,才构造出了矩阵。我们将矩阵从上往下数的第一个子段称为第一行,那么一下所有字段的位置都是确定的了。那么我们就可以更具这个性质压缩一个矩阵。详见下图:
按照这个方法,我们把这个矩阵的第一行确定(假设是 $i$ )后,假设当前矩阵是 $k$ 行,那么就把给出的矩阵中的第 $i$ 至 $i + k -1 $ 行拿出来压缩。然后对于压缩出来的东西求最大子段和就可以了
我们设 $dp[i]$ 表示宽度(每一行的长度)为 $i$ 的最大子矩阵,答案就从所有里面最大的找。详见代码。
代码12345678910111213141516171819202122232 ...
POJ1742 Coins
题面大秦 为你打开 题目传送门
题目翻译:
有 $n$ 种面值的硬币,面值分别为 $a_1,a_2,…,a_n$ ,数量分别为 $c_1,c_2,c_3,…,c_n$ ,请问在这些硬币的组合下,能够组成多少种不超过 $m$ 的面值。
思路显而易见的,这是一道多重背包模板题。面值是体积,数量就是每一种物品数量,$n$ 是物品个数。
但是这里的题目是 “能够组成多少种不超过 $m$ 的面值 ”,所以设定如下状态:
12dp[i]// 表示是否可以组成面值为 i 的方案
往下就是正常步骤了,详见代码。
代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748#include<bits/stdc++.h>using namespace std;int n, m;int a[110];int c[110];int top;int v[100010];int dp[100010];void Input() { scanf("%d%d&quo ...
多重背包
前置知识01背包 和 完全背包 喵!
概念:什么是多重背包相较于01背包和完全背包,更实际的,多重背包是每一个物品有个数限制的背包,这相较于前面两个就有一点点难度了
思路:设计多重背包首先,最最最基础的,我们可以像完全背包那样,暴力枚举每一个物品选多少次,代码时间复杂度是三次方的。
1234for(int i = 1; i <= n; i++) // 枚举物品 for(int j = 0; j <= v; j++) // 枚举体积 for(int k = 0; k <= s[i] && k * c[i] <= j; k++) // 枚举决策 dp[i][j] = max(dp[i][j], dp[i - 1][j - k * c[i]] + k * w[i]);
那么,我们考虑更优的方式去选择物品。
当你面对一些奇奇怪怪的优化分组没有思路的时候,你就可以稍微考虑考虑二进制。二进制拆分,因为二进制转变为十进制的唯一性,所以我们对于物品数量二进制拆分可以优化到 $O(n\log n)$ ,代码如下:
1234 ...
完全背包
前置知识:01背包点击我跳转喵!
概念:完全背包相对于01背包,完全背包是物品数量无限的背包,也就是说,一个物品,我们想要拿多少拿多少(只要背包装得下)。
这就很不一样了,我们具体来看看 $\tt DP$ 怎么推。
思路:构建 $\tt DP$想要让数量无限,我们可以同样设置和 $01$ 背包一样的状态,也就是:
12dp[i][j];// 表示:前 i 个物品里选择出体积为 j 的最大价值
那么问题就是,如何在这个的基础上,做到每一个物品选好多次。
题外话:一个粗劣的算法
所谓数量无限其实只是你背包装不下,也就是说,从某种意义上来讲,我们可以在加一层循环,枚举某一个物品买了几次的01背包。
1234for(int i = 1; i <= n; i++) // 枚举物品 for(int j = 0; j <= v; j++) // 枚举体积 for(int k = 0; k * c[i] <= j; k++) // 三重循环 枚举每种取用件数*c[i]不大于当前总体积j dp[i][j] = max(dp[i][j], dp ...
01背包
概念:什么是01背包01背包,顾名思义,每一个项不是 $0$ 就是 $1$ 的背包。显而易见,就是选和不选的背包。
那就是说,这是一个每个物品只有一个,且只能选货不选得出最大价值的背包问题
证明:怎么做01背包先来看看这道题,显而易见有两种可能,一是贪心,二是 $\tt DP$。那么是哪一种呢?不用想太多,我们可以感性理解。
假设你在买东西,对于一样的物品,你怎么选?肯定是选性价比高的啊!性价比就是性能和价格的比值,体现在这个题目中,他给我们造成的收益是性能,也就是购买这个物品的价值;我们选择它花费的代价是价格,也就是体积。
所以如果要贪心,一种方法就是按照性价比排序,所以说我们或许只需要出一个数据卡掉他,他就是DP题了。
1234570 3 // 背包体积 物品数量// 物品体积 物品价值60 600 // 性价比:1020 180 // 性价比:950 430 // 性价比:8.6
按照贪心的方法,他选了 $60$ 就不可以再次选了,但是我们知道最优解是选 $20 + 50$ 。这也告诉我们这是一道 $\tt DP$ 而非贪心。
推导:如何设计01背包首先从最简单的开始 ...
HDU4597 拾牌游戏
题面大秦为你打开题目传送门
题目描述两人轮流从两堆牌中抽取最顶端或者最底部的牌,得到的分数加到自己身上,假设两个人都绝顶聪明,两个人都想让自己的得分最大,问先拿牌的最多能得多少分。
题解首先拿到这道题,要考虑怎么设置状态。
首先,想要记录当前排队状态,因为 $n$ 特别小,所以只需要直接开 $dp[l1][r1][l2][r2]$ 分别表示两个数组被取成这样的方案就可以了。但是还有一个问题就是要不要分开讨论两个人。
显然不用,因为题目给出要求 “假设两个人都绝顶聪明” 所以每个人都走最优方案就可以了。
代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include<bits/stdc++.h>using namespace std;int n;int a[25], b[25];int sum;int dp[25][25][25][25];void Input() { scanf("%d", &n ...
树的重心
树的重心的概念如果在树中选择某个节点并删除,这棵树将分为若干棵子树,统计子树节点数并记录最大值。取遍树上所有节点,使此最大值取到最小的节点被称为整个树的重心。
说白了就是删去一个点,使这一颗树碎成的块块大小最平均的那一个点,就是重心。
树的重心的代码我们只需要把每一个子树的大小都处理出来,在该节点的父亲方向的点可以用总量减去所有的儿子方向的子树。处理出所有子树的最大值,按照定义模拟即可。
值得注意的是,一个树可以有多个重心,但是他们一定相邻。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include<bits/stdc++.h>using namespace std;int n;vector<int >e[10010];int size[10010];int g[10010];void Input(){ scanf("%d", &n); int u, v; for(int i= ...
并查集
需求:问题需要问题$\tt A$ 是 $\tt B$ 的亲戚,$\tt B$ 是 $\tt C$ 的亲戚,那么 $\tt A$ 是 $\tt C$ 的亲戚,给出若干个这样的关系,问某两个人是亲戚吗?
思考想要实现这样的关系,我们可以先不往算法的方向想,我们可以想如果这是小学数学题你怎么办,考虑如下几种情况:
这样就是我们要说的并查集的基本原理了,只不过写成代码还有亿点点路程,让我们继续推
想要实现这样的功能我们为什么不用 $\tt set$ 集合呢,似乎比我们想要的还要更加直观。当然不行,$\tt set$ 的本质也是树啊(,而且如果我在这个并查集上面要带权值呢(
所以我们这就是要用树形结构,思路也很好懂,比如说我这个 $\tt A$ 是 $\tt B$ 的亲戚,我就可以把 $\tt A$ 的父亲设为 $\tt B$ 这样的操作。
概念 并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
编写:实现思想基础思路用刚刚的思路就可以写出如下代码:
12345int fa[N];int find(int x) { ...