NOIP 前刷水记(
来源:互联网 发布:edrawmax mac 激活码 编辑:程序博客网 时间:2024/05/22 08:23
线段树模板
Luogu P1137 旅行计划
首先 这是一道图论题
读题很重要!!!
读完题后 我们会发现 这是一张有向图 并且是一个“横向”的图 题目要求只能从西向东走,我们只需要进行一次拓扑排序(topsort) 然后用拓扑序进行DP就好了
拓扑排序:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
我们只需要在读入时将被指向的点入度+1 在top sort 时首先将入度为0的点入队,枚举它的每一条边,这些边指向的点的入度-1,当某一个点入度为0时 让他进队就好了
到达 u 这一点可能有多种走法,所以要找最大值
欧拉回路学习总结
若一个联通图中所有点的度数都是偶数,则这个图是欧拉回路。
若一个联通图中只有 0 个点或 2 个点的度数为奇数,则这个图是欧拉路径。
Luogu P1341 无序字母对
这题的意思是,输出一条路径,使得图不经过重复的边。
这是一个欧拉路问题,把每两个点间加一个无向边,判断每个点的度是否为偶数(欧拉回路),或者是否有两个点是奇数(欧拉路径)。
因为要输出前面的字母的 ASCII 编码尽可能小的(字典序最小)的方案,所以找到欧拉路径的起点或回路的字典序最小点,开始 dfs。
DP
于是 … Kumii 就滚去 Menci 博客里找题做啦 … 我才不会告诉你我是倒着做的
「CodeVS 3269」混合背包 - 背包 DP
背包九讲
「CodeVS 3162」抄书问题 - 区间DP
(
求出
题目要求靠前的人尽可能抄的少
那么从后往前扫一遍,从后往前分成一个个区间,每个区间不超过
「NOIP2000」乘积最大 - 区间 DP
「CodeVS 2598」编辑距离问题 - 线性 DP
区间 DP
Luogu P1040 加分二叉树
我们发现,中序遍历区间内任何一点都可以当做根,根左边的部分一定是左子树,根右边的一定是右子树。
我们可以区间 DP 枚举根,然后开个数组记录根。先序遍历时就按照根的顺序递归二分输出根就好了。
注意
#include <bits/stdc++.h>using namespace std;const int N = 35;int n;int root[N][N];long long f[N][N];void dp() { for(int i = n - 1; i >= 1; i --) //倒序! for(int j = i + 1; j <= n; j ++) for(int k = i; k <= j; k ++) { if(i + 1 == j){ f[i][j] = f[i][i] + f[j][j]; root[i][j] = i; } else if(f[i][k - 1] * f[k + 1][j] + f[k][k] > f[i][j]) { f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k]; root[i][j] = k; // 注意只有当满足条件以后再设跟为 k } }}void dfs(int l, int r) { if(l == r){ printf("%d ", l); return; } if(l > r)return; int x = root[l][r]; printf("%d ", x); dfs(l, x - 1); dfs(x + 1, r); return ;}int main() { scanf("%d", &n); for(int i = 1; i <= n; i ++) scanf("%lld", &f[i][i]); dp(); printf("%lld\n", f[1][n]); dfs(1, n); return 0;}
线性 DP
Luogu P1564 膜拜 / 小 ly 的题解 —— 膜拜 小 ly)))
记膜拜神牛甲的人为 1,膜拜神牛乙的人为 -1。
题目就变成了将序列最少分为多少段,使每段和的绝对值
设
(从
边界:
Luogu P2679 子串 / (((小 ly 的题解 —— 膜拜 小 ly
其实是我粘的 luogu 的题解 qwq)
当然直接开
但这道题我们可以倒序循环来压维。把
#include <bits/stdc++.h>using namespace std;const int MOD = 1e9 + 7, N = 1005;char a[N], b[N];int s[N][N], f[N][N];int main() { int n, m, K; scanf("%d%d%d", &n, &m, &K); scanf("%s", a), scanf("%s", b); s[0][0] = 1, f[0][0] = 1; // 初始状态什么都不选的情况下是 1 for(int i = 1; i <= n; i ++) for(int j = m; j >= 1; j --) for(int k = 1; k <= K; k ++) { if(a[i - 1] != b[j - 1]) { // 因为 i j 表示的是 a b 两个字符串的位置,所以要 - 1 s[j][k] = 0; continue; } s[j][k] = (f[j - 1][k - 1] + s[j - 1][k]) % MOD; f[j][k] = (s[j][k] + f[j][k]) % MOD; } printf("%d\n", f[m][K] % MOD); // 最后一个数可能不选,所以是 f[][] return 0;}
树形 DP
「CTSC1997」选课 - 树形 DP
这里的依赖关系是以森林的形式给出的,我们增加一个虚拟节点作为所有无先修课的课程的父节点,搜索这棵树,用
- 给第
i 个节点和它的一个或多个子节点分配一定的课程数量k ,剩余课程数量m−k−1 分给下一个兄弟节点。 - 不选择第
i 个节点,全部课程数量m 分配给下一个兄弟节点。
Luogu P2015 二叉苹果树
(其实是和上面那道选课是一样的呀!
注意: 因为你是要保留
#include<bits/stdc++.h>using namespace std;const int MAXN = 205;int head[MAXN], cnt = 0;struct edge { int to, next, w;}e[MAXN << 1];inline void add(int u, int v, int w) { e[++ cnt] = (edge){v, head[u], w}; head[u] = cnt;}int n, m;int dw[MAXN], size[MAXN];int dp[MAXN][MAXN], lson[MAXN], rson[MAXN]; // lson[u] 是 u 的左儿子是谁 // dw[i] 是 i 的点权,把 x -> y 的边权放到 y 这个点上 void dfs1(int u, int fa) { size[u] = 1; for(int i = head[u]; i; i = e[i].next) { int v = e[i].to; if(v == fa)continue; dw[v] = e[i].w; if(!lson[u]) lson[u] = v; else rson[u] = v; dfs1(v, u); size[u] += size[v]; }}// dp[i][j] = max(dp[i][j], dp[lson][j - 1 - k] + dp[rson][k] + w[i]) 0 <= k <= j - 1// dp[i][j] 表示在 i 这个点,选 j 个点(这 j 个点包含第 i 个点,也就是说选 i 这个点并在 i 的子树里选 j - 1 个点)的最多苹果个数 void dfs2(int u, int fa) { for(int i = head[u]; i; i = e[i].next) { int v = e[i].to; if(v == fa)continue; dfs2(v, u); for(int j = 2; j <= size[u]; j ++) { for(int k = 0; k <= j - 1; k ++) { dp[u][j] = max(dp[u][j], dp[lson[u]][j - 1 - k] + dp[rson[u]][k] + dw[u]); } } }}int main() { memset(dw, 0, sizeof(dw)); scanf("%d%d", &n, &m); for(int i = 1; i < n; i ++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); add(x, y, z), add(y, x, z); } dfs1(1, 1); for(int i = 1; i <= n; i ++) dp[i][1] = dw[i]; dfs2(1, 1); printf("%d\n", dp[1][m + 1]); // 因为你是要保留 m 条边,但你把边权转化成了点权,所以是保留 m + 1 个点 return 0;}
Luogu P1270 “访问”美术馆
emmmmmm
其实 … 和上面两道题是一样的 …
输入格式是深搜顺序,这里我们可以递归调用来加边建树。
图论
Luogu P3387 【模板】缩点
题目:
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
分析:
缩点模板,先用
接下来有两种做法。
法一:
显然最长路径一定是以缩点后入度为 0 的点开始的,所以只需要对缩点后入度为 0 的点跑一遍
因为
法二:
只要在
#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 5, M = 1e5 + 5;int head[N];int stk[N];int x[N], y[N];int dfn[N], low[N], col[N];int f[N], sum[N], w[N];bool insta[N];struct Edge { int to, next;}e[M << 1];int cnt = 0, timer = 0;int top = 0, tot = 0;void add(int x, int y) { e[++ cnt].to = y; e[cnt].next = head[x]; head[x] = cnt;}void tarjan(int x) { dfn[x] = low[x] = ++ timer; stk[++ top] = x; insta[x] = 1; for(int i = head[x]; i; i = e[i].next) { int v = e[i].to; if(!dfn[v]) { tarjan(v); low[x] = min(low[x], low[v]); } else if(insta[v]) low[x] = min(low[x], dfn[v]); } if(low[x] == dfn[x]) { ++ tot; int cmp = 0; while(cmp != x) { cmp = stk[top --]; col[cmp] = tot; sum[tot] += w[cmp]; insta[cmp] = 0; } }}void search(int x) { if(f[x]) return ; f[x] = sum[x]; int maxsum = 0; for(int i = head[x]; i; i = e[i].next) { int v = e[i].to; if(!f[v]) search(v); maxsum = max(maxsum, f[v]); } f[x] += maxsum;}int main() { memset(head, 0, sizeof(head)); memset(f, 0, sizeof(f)); memset(sum, 0, sizeof(sum)); memset(insta,0,sizeof(insta)); int n, m, ans = 0; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) scanf("%d", &w[i]); for(int i = 1; i <= m; i ++) { scanf("%d%d", &x[i], &y[i]); add(x[i], y[i]); } for(int i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i); memset(head, 0, sizeof(head)); memset(e, 0, sizeof(e)); for(int i = 1; i <= m; i ++) if(col[x[i]] != col[y[i]]) add(col[x[i]], col[y[i]]); for(int i = 1; i <= tot; i ++) { if(!f[i]) { search(i); ans = max(ans, f[i]); } } printf("%d\n", ans); return 0;}/*10 20970 369 910 889 470 106 658 659 916 964 3 23 63 49 58 35 89 19 79 87 53 77 81 710 21 104 82 63 13 58 5*/
贪心
Luogu P1970 花匠
分析:
法一:贪心
输入的花盆高度一定是由多个相连续的单调递增或递减的区间构成的
什么是多个相连续的单调递增或递减的区间呢 …
比如 7 10 17 16 18 17 1 2 3
就是由 |7 10 17| |17 16| |16 18| | 18 17| |17 1| | 1 2 3| 这几个连续的区间构成的
我们每次选取这样的一个区间的两端的高度即可
(因为区间是单调递增或递减的,所以两端的高度一定是最大或最小的,这样选就能保证一定最优,正确性显然)
法二:DP
emmmmm
其实和贪心思想一样
明白了贪心思想,这个 DP 应该是很显然的了 …
规定
(1 表示该找下降的花朵了,0 表示该找上升的花朵了)
最后答案就是
if(a[i] > a[i - 1]) f[i][0] = f[i - 1][1] + 1;else f[i][0] = f[i - 1][0];if(a[i] < a[i - 1]) f[i][1] = f[i - 1][0] + 1;else f[i][1] = f[i - 1][1];
Luogu P1080 国王游戏
分析: 贪心
如果第
所以就是使
所以就是
然后高精度部分压位
乘法部分相当于高精度乘低精度
除法部分相当于高精度除低精度
高精度部分懒得写了
#include <bits/stdc++.h>using namespace std;const int N = 1005;int sum[N];struct Score { int x, y;}a[N];bool cmp(Score a, Score b) { return a.x * a.y < b.x * b.y;}int main() { int n; scanf("%d", &n); scanf("%d%d", &a[1].x, &a[1].y); for(int i = 2; i <= n + 1; i ++) scanf("%d%d", &a[i].x, &a[i].y); sort(a + 2, a + n + 2, cmp); // 注意是到 a + n + 2 sum[1] = a[1].x; for(int i = 2; i <= n + 1; i ++) sum[i] = sum[i - 1] * a[i].x; int f, ans = 0; for(int i = 2; i <= n + 1; i ++) { f = sum[i - 1] / a[i].y; ans = max(ans, f); } printf("%d\n", ans); return 0;}
- NOIP 前刷水记(
- Noip
- noip
- NOIP
- NOIP
- NOIP随笔
- noip 虫食算
- 冲刺NOIP!
- NOIP 完挂
- Noip冲刺
- NOIP复习计划
- noip 2014
- NOIP后
- noip总结
- My NOIP
- NOIP-字符串
- noip 2006
- NOIP 2003
- Mac下PyCharm切换虚拟环境
- bash常用文本处理工具
- ES6-数值的扩展-Number.isInteger() 和 Number.EPSILON
- 关于矩阵分解:特征值分解 svd分解 mf分解 lmf分解 pca 以及个性化推荐 fm ffm als
- opencv----滤波函数:方框滤波、均值滤波、高斯滤波、中值滤波、双边滤波
- NOIP 前刷水记(
- 通过两个位置的经纬度坐标计算距离(C#版本)
- iOS AVPlayer音频播放、缓存、歌词同步 (DFPlayer的使用方法)
- 总结——Java大数模板应用
- 回归Java:泛型使用
- C#线程同步的几种方法
- 5.2
- php自动加载
- Java 如何有效地避免OOM:善于利用软引用和弱引用