如果细说的话,这个专题应该叫推公式+排序。其中推公式就是寻找排序规则,排序就是在该排序规则下对整个对象排序。
在解决某些问题的时,当我们发现最终结果需要调整每个对象的先后顺序,也就是对整个对象排序时,那么我们就可以⽤推公式的⽅式,得出我们的排序规则,进⽽对整个对象排序。
正确性证明:
利⽤排序解决问题,最重要的就是需要证明"在新的排序规则下,整个集合可以排序"。这需要⽤到离散数学中"全序关系"的知识。我会在第⼀道题中证明该题的排序规则下,整个集合是可以排序的。
但是证明过程很⿇烦,后续题⽬中我们只要发现该题最终结果需要排序,并且交换相邻两个元素的时候,对其余元素不会产⽣影响,那么我们就可以推导出排序的规则,然后直接去排序,就不去证明了
我们发现,任取序列⾥⾯相邻的两项a[i], a[i+1]
,交换他们的顺序,并不影响[1,i-1]
与[i+1,n]
之间每⼀位的权值。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设两个相邻的数对应的字符串形式为x, y ,因为要的是最⼤值,所以⾃定义⽐较⽅式:
#include
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;
}
我们发现,在在⼀个序列中,任意交换相邻两头⽜a[i],a[i+1]
的顺序之后,区间[1,i-1]
以及[i+1,n]
i内所有⽜吃草的总量不变。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设交换的相邻两头⽜的下标为i, j
(i + 1 = j) ,到达i 位置时,经过的时间为T 。由此可得:
#include
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;
}
我们发现,在⼀个序列中,任意交换相邻两头⽜a[i], a[i+1]
的顺序之后,区间[1,i-1]
以及[i+1,n]
内每⼀头⽜的压扁指数都是不变。因此我们可以找⼀种⽐较⽅式,对整个数组排序,最终的结果就是最优序列。
设交换的相邻两头⽜的下标为i,j
(i+1 = j),[1,i-1]
区间内所有奶⽜的重量为W 。由此可得:
#include
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 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 i + s i < w j + s j w_{i}+s_{i}
#include
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个带权叶结点的⼆叉树中,其中带权路径⻓度最⼩的⼆叉树称为哈夫曼树,也称最优⼆叉树
哈夫曼算法是哈夫曼树的构建过程,是根据贪⼼策略得到的算法。主要流程为:
在构建哈夫曼树的合并操作中,就可以计算出带权路径⻓度:
哈夫曼编码是⼀种被⼴泛应⽤⽽且⾮常有效的数据压缩编码,其构造步骤如下:
每次拿出权值最⼩的两颗树合并,然后将合并后的树继续放回集合中,直到集合中只剩下⼀棵树
#include
using namespace std;
typedef long long LL;
int n;
priority_queue, greater> 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;
}
每次拿出最⼩的两堆合并
#include
using namespace std;
typedef long long LL;
int n;
priority_queue, greater> 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;
}