DP优化

优化DP笔记

P6040 「ACOI2020」课后期末考试滑溜滑溜补习班

fi 表示老师解决到第 i 个学生需要最少的精力,答案显然是 fn

边界 : 对于 i=1 , f1=a1

对于其他的 i 号学生,我们假设老师是从第 j 位学生过来的,所以状态转移方程分为三项

  1. fj 表示之前的值
  2. (ij1)×d+k 表示应对调侃和移动花费的精力
  3. ai 是解决当前学生问题花费的精力

整合起来方程就为

fi=min{fj+(ij1)×d+k+ai}

当然 j 是有范围的 ixj<ij=i1 时,实际上就是没有跳过

对于每个fi ,只需要寻找最小的 fj 转移过来就可以了,但这样要循环 ij ,时间复杂度为 O(n2)

然后对于 [ix,i1] 的区间内寻找最小值,我们可以用单调队列可以维护求出来

fj 提出来即可

fi=ai+k+(i1)×d+min{dpjj×d}

接下来我们维护 fjj×d

#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 1e7 + 10; inline int read() { int x = 0, f = 1; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); } while (s >= '0' && s <= '9') { x = (x << 3) + (x << 1) + (s ^ 48); s = getchar(); } return x * f; } LL n, k, d, x, tp, seed; LL a[N], q[N], f[N]; inline LL rnd() { static const int MOD = 1e9; return seed = (1LL) * (seed * 0x66CCFF % MOD + 20120712) % MOD; } int main() { cin >> n >> k >> d >> x >> tp; if (tp) { seed = read(); for (int i = 1; i <= n; i++) a[i] = rnd(); } else { for (int i = 1; i <= n; i++) a[i] = read(); } f[1] = a[1]; int head = 1, tail = 1; q[1] = 1; for (int i = 2; i <= n; i++) { while (head <= tail && q[head] < i - x) head++; f[i] = f[q[head]] + a[i] + k + (i - q[head] - 1) * d; while (head <= tail && f[q[tail]] - q[tail] * d >= f[i] - i * d) tail--; q[++tail] = i; } cout << f[n] << endl; return 0; }

P2168 [NOI2015] 荷马史诗

这道题的本体为哈夫曼数。

以二叉哈夫曼树为例,给 n 个点,构造一棵哈夫曼树,每次选剩下的两棵权值最小的树合并成一棵新树,新树的根权值等于前两棵合并的树的根权值和

例如下图

例一

(a图):四个点 a,b,c,d,权值分别为7,5,2,4

(b图):先把 2(c)4(d) 合并,新的根节点值为 6

(c图):再把 5(b)6(c+d) 合并,新的根节点为 11

(d图):最后把 7(a)11(b+c+d) 合并,新的根节点为 18

那么这道题便是 k 叉哈夫曼树,但如果真正去写就会发现一种情况:

当最后一次合并之后,可能树上只剩下 m 个节点,且 1<m<k 无法再合并

所以我们可以用补点的方法这个方法:

首先,我们每次合并用掉 k 个值又合并出来一个值,那么按每次合并来算,我们每次用掉了 k1 个值,然后,我们最终需要只剩 1 个值,也就是需要合并 n1 个值,所以我们只要判断 这个东西(n1)mod(k1)=0 成立,我们还需要补 (k1)((n1)mod(k1) 个点

接下来我们看一个例子

原文为:AMCADEDDMCCAD

我们发现只有五个字母 E,M,C,A,D ,使用频率为 {1,2,3,3,4}

img

绿色为原来的点,白色为补点,01 表示左右儿子

哈夫曼编码为

E=000,I=001,C=01,A=10,D=11

答案为树的所有叶子节点的带权路径长度之和与树的最大深度

因为Huffman每次合并都要排序,所以使用堆(直接用priority_queue更方便)。

#include <bits/stdc++.h> using namespace std; typedef long long LL; struct node { LL w, h; //表示权值和高度 bool operator<(const node &x) const { //重载运算符 if (w != x.w) //优先考虑权值 return w > x.w; return h > x.h; } }; priority_queue<node> haff; LL n, k, ans1, ans2, x; //单词数量、进制 //重新编码以后的最短长度、最长字符串s[i]的最短长度 int main() { cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> x; haff.push(node{x, 1}); //默认高度为1 } if ((n - 1) % (k - 1)) //判断是否需要补点 { x = k - 1 - (n - 1) % (k - 1); //要补的点数 for (int i = 1; i <= x; i++) haff.push((node){0, 1}); //补点 } LL sum, maxx; //临时答案 while (haff.size() != 1) //因为不可以是负数,所以大于1个点就跑 { sum = 0, maxx = -2147483646; for (int i = 1; i <= k; i++) //每次选出k个数 { sum += haff.top().w; //加上队首 maxx = max(haff.top().h, maxx); //找最小长度 haff.pop(); //队头出队 } haff.push((node){sum, maxx + 1}); //加上合并的值 ans1 += sum; ans2 = max(ans2, maxx); } cout << ans1 << endl << ans2; return 0; }

「DPOI-1」道路规划

[USACO04DEC] Dividing the Path G


__EOF__

本文作者ljfyyds
本文链接https://www.cnblogs.com/ljfyyds/p/17529634.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   ljfyyds  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示