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} W−si;
第j头⽜的压扁指数为: W + w i − s j W+w_{i}-s_{j} W+wi−sj;
被压得最扁的那头奶⽜的压扁指数: 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(W−si,W+wi−si) - 交换后:
第j 头⽜的压扁指数为: W − s j W-s_{j} W−sj
第i 头⽜的压扁指数为: W + w j − s i W+w_{j}-s_{i} W+wj−si
被压得最扁的那头奶⽜的压扁指数: 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(W−sj,W+wj−si)
由于 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,wi−sj)<max(−sj,wj−si)。
#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,wi−sj)<max(−sj,wj−si)
第一个max一定不会取
−
s
i
-s_{i}
−si,因为
−
s
i
-s_{i}
−si一定比
w
j
−
s
i
w_{j}-s_{i}
wj−si小,就没什么意义
然后第二个max也不可能取
−
s
j
-s_{j}
−sj,这样
w
i
−
s
j
w_{i}-s_{j}
wi−sj一定比
−
s
j
-s_{j}
−sj大,就违反小于号了。
所以可以化解为
w
i
−
s
j
<
w
j
−
s
i
w_{i}-s_{j}<w_{j}-s_{i}
wi−sj<wj−si
也就是
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=1∑nwili
其中,
w
i
w_{i}
wi是第i个叶结点所带的权值,
l
i
l_{i}
li是该叶结点到根结点的路径⻓度
哈夫曼树
在含有n个带权叶结点的⼆叉树中,其中带权路径⻓度最⼩的⼆叉树称为哈夫曼树,也称最优⼆叉树
哈夫曼算法
哈夫曼算法是哈夫曼树的构建过程,是根据贪⼼策略得到的算法。主要流程为:
- 初始化:将所有叶⼦结点看做⼀棵棵树,那么刚开始我们有⼀⽚森林;
- 贪⼼:每次选择根节点权值最⼩的两棵树作为左右⼦树合并成⼀棵新的⼆叉树,这棵新的⼆叉树根节点的权值为左右⼦树的权值之和;
- 重复2 过程,直到森林中所有的树合并成⼀棵树。
在构建哈夫曼树的合并操作中,就可以计算出带权路径⻓度:
- 在合并的过程中,每⼀棵树的根节点的权值其实等于该树所有叶⼦结点的权值之和;
- 在每次合并的时候,由于多出来两条路径,此时累加上左右⼦树的根节点权值,相当于计算了⼀次叶⼦结点到这两条路径的⻓度;
- 每次合并都把左右⼦树的权值累加起来,就是最终的带权路径⻓度。
哈夫曼编码
哈夫曼编码是⼀种被⼴泛应⽤⽽且⾮常有效的数据压缩编码,其构造步骤如下:
- 统计待编码的序列中,每⼀个字符出现的次数;
- 将所有的次数当成叶结点,构造哈夫曼树;
- 规定哈夫曼树的左分⽀为0,右分⽀为1,那么从根节点⾛到叶⼦结点的序列,就是该叶⼦结点对应字符的编码
哈夫曼编码
每次拿出权值最⼩的两颗树合并,然后将合并后的树继续放回集合中,直到集合中只剩下⼀棵树
#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;
}