当前位置: 首页 > news >正文

NO.65十六届蓝桥杯备战|基础算法-贪心推公式排序|哈夫曼编码|拼数|奶牛玩杂技|哈夫曼编码|合并果子(C++)

推公式排序

推公式

如果细说的话,这个专题应该叫推公式+排序。其中推公式就是寻找排序规则,排序就是在该排序规则下对整个对象排序。
在解决某些问题的时,当我们发现最终结果需要调整每个对象的先后顺序,也就是对整个对象排序时,那么我们就可以⽤推公式的⽅式,得出我们的排序规则,进⽽对整个对象排序。
正确性证明:
利⽤排序解决问题,最重要的就是需要证明"在新的排序规则下,整个集合可以排序"。这需要⽤到离散数学中"全序关系"的知识。我会在第⼀道题中证明该题的排序规则下,整个集合是可以排序的。
但是证明过程很⿇烦,后续题⽬中我们只要发现该题最终结果需要排序,并且交换相邻两个元素的时候,对其余元素不会产⽣影响,那么我们就可以推导出排序的规则,然后直接去排序,就不去证明了

P1012 [NOIP 1998 提高组] 拼数 - 洛谷

我们发现,任取序列⾥⾯相邻的两项a[i], a[i+1] ,交换他们的顺序,并不影响[1,i-1][i+1,n]之间每⼀位的权值。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设两个相邻的数对应的字符串形式为x, y ,因为要的是最⼤值,所以⾃定义⽐较⽅式:

  • x + y > y + x :x 放在前⾯,y 放在后⾯;
  • x + y < y + x :y 放在前⾯,x 放在后⾯;
  • x + y = y + x :谁前谁后⽆所谓。
#include <bits/stdc++.h>
using namespace std;

const int N = 25;

int n;
string a[N];

bool cmp(string& x, string& y)
{
    return x + y > y + x;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    sort(a+1, a+1+n, cmp);

    for (int i = 1; i <= n; i++) cout << a[i];
    
    return 0;
}
P2878 [USACO07JAN] Protecting the Flowers S - 洛谷

我们发现,在在⼀个序列中,任意交换相邻两头⽜a[i],a[i+1]的顺序之后,区间[1,i-1]以及[i+1,n]i内所有⽜吃草的总量不变。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设交换的相邻两头⽜的下标为i, j (i + 1 = j) ,到达i 位置时,经过的时间为T 。由此可得:

  • 交换前:
    第i 头⽜吃草量: T × d i T \times d_{i} T×di
    第j 头⽜的吃草量: ( T + 2 × t i ) × d j (T+2\times t_{i}) \times d_{j} (T+2×ti)×dj
    消耗的总时⻓: ∑ 1 = T d i + T d j + 2 t i d j \sum_{1}=Td_{i}+Td_{j}+2t_{i}d_{j} 1=Tdi+Tdj+2tidj
  • 交换后:
    第j 头⽜吃草量: T × d j T \times d_{j} T×dj
    第i 头⽜的吃草量: ( T + 2 × t j ) × d i (T+2\times t_{j})\times d_{i} (T+2×tj)×di
    消耗的总时⻓: ∑ 2 = T d j + T d i + 2 t j d i \sum_{2}=Td_{j}+Td_{i}+2t_{j}d_{i} 2=Tdj+Tdi+2tjdi
    如果i 在j 前⽐较好,对应的sum1 应该⼩于sum2 ,也就是 t i d j < t j d i t_{i}d_{j} < t_{j} d_{i} tidj<tjdi
    综上,我们⾃定义的⽐较⽅式为:
  • t i d j < t j d i t_{i}d_{j}<t_{j}d_{i} tidj<tjdi时:i在j 前;
  • t i d j > t j d i t_{i}d_{j}>t_{j}d_{i} tidj>tjdi时:i在j 后;
  • t i d j = t j d i t_{i}d_{j}=t_{j}d_{i} tidj=tjdi时:谁前谁后⽆所谓
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
struct node
{
    int t;
    int d;
}a[N];

bool cmp(node& x, node& y)
{
    return x.t * y.d < y.t * x.d;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].t >> a[i].d;        
    }
    sort(a+1, a+1+n, cmp);

    LL ret = 0, t = 0;
    for (int i = 1; i <= n; i++)
    {
        ret += a[i].d * t;
        t += 2 * a[i].t;
    }
    cout << ret << endl;
    
    return 0;
}
P1842 [USACO05NOV] 奶牛玩杂技 - 洛谷

我们发现,在⼀个序列中,任意交换相邻两头⽜a[i], a[i+1]的顺序之后,区间[1,i-1]以及[i+1,n]内每⼀头⽜的压扁指数都是不变。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设交换的相邻两头⽜的下标为i,j(i+1 = j),[1,i-1]区间内所有奶⽜的重量为W 。由此可得:

  • 交换前:
    第i 头⽜的压扁指数为: W − s i W-s_{i} Wsi
    第j头⽜的压扁指数为: W + w i − s j W+w_{i}-s_{j} W+wisj
    被压得最扁的那头奶⽜的压扁指数: k 1 = m a x ( W − s i , W + w i − s i ) k_{1}=max(W-s_{i}, W+w_{i}-s_{i}) k1=max(Wsi,W+wisi)
  • 交换后:
    第j 头⽜的压扁指数为: W − s j W-s_{j} Wsj
    第i 头⽜的压扁指数为: W + w j − s i W+w_{j}-s_{i} W+wjsi
    被压得最扁的那头奶⽜的压扁指数: k 2 = m a x ( W − s j , W + w j − s i ) k_{2}=max(W-s_{j},W+w_{j}-s_{i}) k2=max(Wsj,W+wjsi)
    由于 k 1 , k 2 k_{1},k_{2} k1,k2⾥⾯都包含W,可以统⼀删除。如果i在⽐j在前⽐较好,也就是总体的压扁指数要尽可能的⼩。那么得要求 k 1 < k 2 k_{1}<k_{2} k1<k2,也就是: m a x ( − s i , w i − s j ) < m a x ( − s j , w j − s i ) max(-s_{i},w_{i}-s_{j})<max(-s_{j},w_{j}-s_{i}) max(si,wisj)<max(sj,wjsi)
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
struct node
{
    int w;
    int s;
}a[N];

bool cmp(node& i, node& j)
{
    return max(-i.s, i.w-j.s) < max(j.w-i.s, -j.s);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].w >> a[i].s;

    sort(a+1, a+1+n, cmp);

    LL ret = -1e9-10, w = 0;
    for (int i = 1; i <= n; i++)
    {
        ret = max(ret, w - a[i].s);
        w += a[i].w;
    }
    cout << ret << endl;
    
    return 0;
}

其实 m a x ( − s i , w i − s j ) < m a x ( − s j , w j − s i ) max(-s_{i},w_{i}-s_{j})<max(-s_{j},w_{j}-s_{i}) max(si,wisj)<max(sj,wjsi)
第一个max一定不会取 − s i -s_{i} si,因为 − s i -s_{i} si一定比 w j − s i w_{j}-s_{i} wjsi小,就没什么意义
然后第二个max也不可能取 − s j -s_{j} sj,这样 w i − s j w_{i}-s_{j} wisj一定比 − s j -s_{j} sj大,就违反小于号了。
所以可以化解为
w i − s j < w j − s i w_{i}-s_{j}<w_{j}-s_{i} wisj<wjsi
也就是
w i + s i < w j + s j w_{i}+s_{i}<w_{j}+s_{j} wi+si<wj+sj

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
struct node
{
    int w;
    int s;
}a[N];

bool cmp(node& i, node& j)
{
    return i.w + i.s < j.w + j.s;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].w >> a[i].s;

    sort(a+1, a+1+n, cmp);

    LL ret = -1e9-10, w = 0;
    for (int i = 1; i <= n; i++)
    {
        ret = max(ret, w - a[i].s);
        w += a[i].w;
    }
    cout << ret << endl;
    
    return 0;
}

哈夫曼编码

树的带权路径⻓度

从树的根到任意结点的路径⻓度与该结点上权值的乘积,称为该结点的带权路径⻓度。树中所有叶结点的带权路径⻓度之和称为该树的带权路径⻓度,记为
W P L = ∑ i = 1 n w i l i WPL=\sum^{n}_{i=1}w_{i}l_{i} WPL=i=1nwili
其中, w i w_{i} wi是第i个叶结点所带的权值, l i l_{i} li是该叶结点到根结点的路径⻓度

哈夫曼树

在含有n个带权叶结点的⼆叉树中,其中带权路径⻓度最⼩的⼆叉树称为哈夫曼树,也称最优⼆叉树

哈夫曼算法

哈夫曼算法是哈夫曼树的构建过程,是根据贪⼼策略得到的算法。主要流程为:

  1. 初始化:将所有叶⼦结点看做⼀棵棵树,那么刚开始我们有⼀⽚森林;
  2. 贪⼼:每次选择根节点权值最⼩的两棵树作为左右⼦树合并成⼀棵新的⼆叉树,这棵新的⼆叉树根节点的权值为左右⼦树的权值之和;
  3. 重复2 过程,直到森林中所有的树合并成⼀棵树。

在构建哈夫曼树的合并操作中,就可以计算出带权路径⻓度:

  • 在合并的过程中,每⼀棵树的根节点的权值其实等于该树所有叶⼦结点的权值之和;
  • 在每次合并的时候,由于多出来两条路径,此时累加上左右⼦树的根节点权值,相当于计算了⼀次叶⼦结点到这两条路径的⻓度;
  • 每次合并都把左右⼦树的权值累加起来,就是最终的带权路径⻓度。
哈夫曼编码

哈夫曼编码是⼀种被⼴泛应⽤⽽且⾮常有效的数据压缩编码,其构造步骤如下:

  1. 统计待编码的序列中,每⼀个字符出现的次数;
  2. 将所有的次数当成叶结点,构造哈夫曼树;
  3. 规定哈夫曼树的左分⽀为0,右分⽀为1,那么从根节点⾛到叶⼦结点的序列,就是该叶⼦结点对应字符的编码
    ![[Pasted image 20250405144023.png]]
哈夫曼编码

每次拿出权值最⼩的两颗树合并,然后将合并后的树继续放回集合中,直到集合中只剩下⼀棵树

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

int n;
priority_queue<LL, vector<LL>, greater<LL>> heap;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        LL x; cin >> x;
        heap.push(x);
    }
    //哈夫曼编码
    LL ret = 0;
    while (heap.size() > 1)
    {
        LL x = heap.top(); heap.pop();
        LL y = heap.top(); heap.pop();
        ret += x + y;
        heap.push(x + y);
    }
    cout << ret << endl;
    
    return 0;
}
P1090 [NOIP 2004 提高组] 合并果子 - 洛谷

每次拿出最⼩的两堆合并

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

int n;
priority_queue<LL, vector<LL>, greater<LL>> heap;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        LL x; cin >> x;
        heap.push(x);
    }
    LL sum = 0;
    while (heap.size() > 1)
    {
        LL x = heap.top(); heap.pop();
        LL y = heap.top(); heap.pop();
        sum += x + y;
        heap.push(x + y);
    }
    cout << sum << endl;
    
    return 0;
}

相关文章:

  • 接口自动化学习二:session自动管理cookie
  • 网络协议:TCP,UDP详细介绍
  • Windows Flip PDF Plus Corporate PDF翻页工具
  • MySQL数据库精研之旅第五期:CRUD的趣味探索(中)
  • py文件打包为exe可执行文件,涉及mysql连接失败以及找不到json文件
  • 使用PyQt5绘制水波浪形的柱状显示流量—学习QTimer+QPainterPath
  • Logo语言的区块链
  • Compose组件转换XML布局1.0
  • 基于SpringBoot的医院信息管理系统(源码+数据库)
  • 基于Python的人脸识别校园考勤系统
  • 初见TypeScript
  • 微信小程序—路由
  • Qt 入门 0 之 QtCreator 简介
  • 【微服务架构】SpringCloud Alibaba(八):Nacos 2.1.0 作为配置中心(Nacos的使用)
  • SpringAI+MCP协议 实战
  • 第六章:框架实战:构建集成向量与图谱的 RAG 应用
  • Qt的window注册表读写以及删除
  • VBA数据库解决方案第二十讲:SQL在VBA中几种常见的表达方式
  • 11.使用依赖注入容器实现松耦合
  • Gerapy二次开发:git项目实现变更拉取与上传
  • 响应式网站怎么做mip/谷歌google官网
  • 网站设计批发/新媒体seo培训
  • 怎么选择顺德网站建设/武汉最新今天的消息
  • 如何为旅游网站店铺做推广营销/企业网站seo多少钱
  • 程序源代码网站/网站seo推广优化
  • 做a免费网站有哪些/做神马seo快速排名软件