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 这一点可能有多种走法,所以要找最大值 ans[u]=max(ans[u],ans[v]+1)


欧拉回路学习总结

若一个联通图中所有点的度数都是偶数,则这个图是欧拉回路。
若一个联通图中只有 0 个点或 2 个点的度数为奇数,则这个图是欧拉路径。

Luogu P1341 无序字母对

这题的意思是,输出一条路径,使得图不经过重复的边。
这是一个欧拉路问题,把每两个点间加一个无向边,判断每个点的度是否为偶数(欧拉回路),或者是否有两个点是奇数(欧拉路径)。
因为要输出前面的字母的 ASCII 编码尽可能小的(字典序最小)的方案,所以找到欧拉路径的起点或回路的字典序最小点,开始 dfs。


DP

于是 … Kumii 就滚去 Menci 博客里找题做啦 … 我才不会告诉你我是倒着做的

「CodeVS 3269」混合背包 - 背包 DP
背包九讲

「CodeVS 3162」抄书问题 - 区间DP

f[i][j] 表示前i个人,抄到第j本书,抄的最多的人最少抄多少
f[i][j]=min(max(f[k1][j1],sum[i]sum[k])) ,sum[] 是维护的前缀和
sum[i]sum[k] 是第i个人抄书的页数,与前i1个人的抄书页数最多的人比 max
求出f[n][K1]n本,分给K个人抄,抄的最多的人最少抄多少

题目要求靠前的人尽可能抄的少
那么从后往前扫一遍,从后往前分成一个个区间,每个区间不超过f[n][K1]就行

「NOIP2000」乘积最大 - 区间 DP

「CodeVS 2598」编辑距离问题 - 线性 DP


区间 DP

Luogu P1040 加分二叉树

我们发现,中序遍历区间内任何一点都可以当做根,根左边的部分一定是左子树,根右边的一定是右子树。
我们可以区间 DP 枚举根,然后开个数组记录根。先序遍历时就按照根的顺序递归二分输出根就好了。

注意 dp 的时候 i 是倒序的,因为如果正序的话第一次循环 i 是 1 后面 j=n 了,这样 f[i][j] 不就已经转移出来了,那你 i=2,i=3 的转移有什么用,所以显然不对。

#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。
题目就变成了将序列最少分为多少段,使每段和的绝对值 M 或等于人数。于是我们可以用前缀和 sum[i] 算出前 i 个数之和。去枚举每一个 j 段即可。

f[i] 表示前 i 个人的最少需要机房数

f[i]=min(min{f[j],abs(sum[i]sum[j1])==ij+1||abs(sum[i]sum[j1])<=m}+1,f[i])
(从 j+1 个人到第 i 个人可以乘一辆车)

边界:f[0]=0f[1]=1

Luogu P2679 子串 / (((小 ly 的题解 —— 膜拜 小 ly



其实是我粘的 luogu 的题解 qwq)

当然直接开 O(nmk) 的数组是肯定开不下的,第一维的 i 显然可以滚掉。
但这道题我们可以倒序循环来压维。把 i 的一维直接压掉。

#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

这里的依赖关系是以森林的形式给出的,我们增加一个虚拟节点作为所有无先修课的课程的父节点,搜索这棵树,用 f[i][m] 表示选择第 i 个节点及其之后节点(兄弟或孩子)中的 m 个节点所对应的课程所获得的最大学分,则有两个转移方向:

  1. 给第 i 个节点和它的一个或多个子节点分配一定的课程数量 k,剩余课程数量 mk1 分给下一个兄弟节点。
  2. 不选择第 i 个节点,全部课程数量 m 分配给下一个兄弟节点。

f[i][m]=max(max{f[i.children][k]+f[i.next][mk1],k[0,m1]},f[i.next][m])

Luogu P2015 二叉苹果树

(其实是和上面那道选课是一样的呀!

dp[i][j]=max(max{dp[lson][j1k]+dp[rson][k]+w[i],k[0,j1]},dp[i][j])
dp[i][j] 表示在 i 这个点,选 j 个点(这 j 个点包含第 i 个点,也就是说选 i 这个点并在 i 的子树里选 j1 个点)的最多苹果个数

注意: 因为你是要保留 m 条边,但你把边权转化成了点权,所以最终答案是保留 m+1 个点的最大苹果个树

#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
其实 … 和上面两道题是一样的 …

输入格式是深搜顺序,这里我们可以递归调用来加边建树。

dp[i][j] 表示 i 为节点拿 j 副画所用最短时间
dp[i][j]=min(min{dp[lson][k]+dp[rson][jk]+2(lson.w+rson.w),k(0,j)},dp[i][j])


图论

Luogu P3387 【模板】缩点

题目

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

n<=104,,m<=105||<=1000

分析

缩点模板,先用Tarjan算法求出强连通分量,将强连通分量里每个点权值加到一起。然后枚举每一条边,如果两点属于不同强连通分量,就在强连通分量之间加一条边。

接下来有两种做法。

法一:SPFA

显然最长路径一定是以缩点后入度为 0 的点开始的,所以只需要对缩点后入度为 0 的点跑一遍spfa,然后取最长路径,即为答案。
因为N<=10000M<=100000Tarjan算法O(N)缩点后,点数边数大大减少,只需要跑入度为 0 的点,所以可以在时间限制内通过。

法二:DAGdp

只要在DAG上做一次动态规划就行了,有点像DP+的感觉,就是缩点后for循环从一个还没有被遍历过的强连通分量开始深搜,对于相邻的没有访问过的强连通分量,继续DFS;对于相邻的访问过的强连通分量,则直接调用其权值,然后在所有强连通分量中取最大的权值,用当前强连通分量中所有点的值累加上这个权值,就是当前强连通分量最优的权值了。

#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 应该是很显然的了 …

规定 f[i][0]f[i][1] 表示以 i 为结尾的,应该寻找上升或者下降的花朵的时候最大能保留的花朵数。
(1 表示该找下降的花朵了,0 表示该找上升的花朵了)
最后答案就是 max(f[n][1],f[n][0]),因为由贪心思想可得,最终答案一定会选最后一个区间的最后一朵花,即第 n 朵花。

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 国王游戏

分析: 贪心

如果第 i 个大臣放第 j 个大臣前面对答案的贡献小些,那么i 个大臣就放第 j 个大臣前面

所以就是使 a[i].x/a[j].y<a[j].x/a[i].y
所以就是 a[i].xa[i].y<a[j].xa[j].y

然后高精度部分压位
乘法部分相当于高精度乘低精度
除法部分相当于高精度除低精度

高精度部分懒得写了

#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;}
原创粉丝点击