区间问题
区间选点
知识点
解题思路
- 贪心算法:
- 对于本道题:
- 先将每个区间的右端点按从小到大进行排序
- 从前往后依次枚举每个区间
- 如果当前区间中已经包含点,则直接pass
- 否则选择当前区间的右端点
- 数学证明:
- 如果想要证明$A=B$,可以分别证明$A\geq B$和$A \leq B$
- 设$\text{cnt}$是所有可能方案的选点数,$\text{Ans}$是所有可能方案中的最小值
- 所以我们有$\text{Ans} \leq \text{cnt}$(这个不用证明)
- (这个根据新加进来的规则进行证明)对于第二种情况“否则选择当前区间的右端点”,如下图所示,对于任意$\text{cnt}$,我们可以找到$\text{cnt}$个区间(从左至右排列,两两彼此之间互不相交)
- 那么最优解的下界就是这些区间上必须都有一个点,才可以覆盖这些区间
- 所以,$\text{Ans} \geq \text{cnt}$
- 综上,$\text{Ans} = \text{cnt}$,即通过这种策略选出来的点就是最优解
实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
int n;
struct Range{
int l, r;
bool operator< (const Range &W) const{
return r < W.r;
}
}range[N];
int main(){
cin >> n;
for(int i = 0; i < n; i ++){
scanf("%d%d", &range[i].l, &range[i].r);
}
sort(range, range + n);
int res = 0, ed = -2e9;
for(int i =0; i < n; i ++){
if(range[i].l > ed){
res ++;
ed = range[i].r;
}
}
cout << res << endl;
return 0;
}
最大不相交区间数量
知识点
解题思路
- 我们的贪心规则是:
- 如果当前区间被已选中的区间部分覆盖了,那么就跳过该区间
- 如果没有被覆盖,那么就选择该区间,并将该区间的右侧端点作为边界值进行判断
- 我们假设按照以上规则选出的区间个数为$Cnt$个,最优解(最大值)的区间个数为$Ans$个
- 那么根据定义,我们可以轻松得到$Ans \geq Cnt$
- 根据我们的贪心规则,已经将所有的不相交的区间挑选出来了,所以最优解的上界就是$Cnt$,所以$Ans \leq Cnt$
- 综上,我们可以得到$Ans = Cnt$,即根据我们的贪心规则,挑选出来的就是最优解
实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
struct Range{
int l, r;
bool operator< (const Range &W) const{
return r < W.r;
}
}range[N];
int main(){
int n;
cin >> n;
for(int i = 0; i < n; i ++)
scanf("%d%d", &range[i].l, &range[i].r);
sort(range, range + n);
int ans = 0, ed = -2e9;
for(int i = 0; i < n; i ++){
if(range[i].l > ed){
ans ++;
ed = range[i].r;
}
}
cout << ans << endl;
return 0;
}
区间分组
知识点
解题思路
- 首先将所有区间按照左端点从小到大进行排序
- 我们的贪心规则为:
- 如果当前区间有一部分(左端点小于组的最右侧端点)与最新建立的组有部分重合,那么就建立一个新的组存放这个区间
- 如果当前区间没有重合,那么就将这个区间放入组中
- 数学证明:
- 假设我们根据这个规则选出来的组有$Cnt$个,最优解(最小值)的区间有$Ans$个
- 那么根据定义,可以得到$Ans \leq Cnt$
- 根据我们指定的规则,我们至少会有$Cnt$个组,这就得到了$Ans$的下界,即$Ans \geq Cnt$
- 假设有$Cnt - 1$个组,那么必定会有一个组具有重叠的部分,这与题设不符合,所以不成立
- 假设有$Cnt + 1$个组,那么就要从一个组选出一个区间,但是这个区间是可以加入原来的组,这与最小值的定义就不相符合了
- 综上,$Ans = Cnt$
实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
struct Range{
int l, r;
bool operator< (const Range &W) const{
return l < W.l;
}
}range[N];
int main(){
int n;
cin >> n;
for(int i = 0; i < n; i ++)
scanf("%d%d", &range[i].l, &range[i].r);
sort(range, range + n);
priority_queue<int, vector<int>, greater<int>> heap;
for(int i = 0; i < n; i ++){
// 由于排序的完成,所有区间都是按照左端点从小到大已经排好序了
// 如果当前区间的左端点小于最新组的右端点
if(heap.empty() || heap.top() >= range[i].l)
heap.push(range[i].r);
else{
heap.pop();
heap.push(range[i].r);
}
}
cout << heap.size() << endl;
return 0;
}
区间覆盖
知识点
解题思路
- 贪心规则:
- 将所有区间按左端点从小到大进行排序
- 从前往后依次枚举每个区间,在所有能覆盖
start
的区间中,选择一个右端点最大的区间,然后将start
更新成右端点的最大值
- 数学证明:
- 设根据规则选出来的区间数为$Cnt$个,最优解(最小值)为$Ans$个
- 根据题意,必有$Ans \leq Cnt$
- 根据我们的规则$Ans \geq Cnt$
实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
struct Range{
int l, r;
bool operator< (const Range &W) const{
return l < W.l;
}
}range[N];
int main(){
int st, ed, n;
cin >> st >> ed;
cin >> n;
for(int i = 0; i < n; i ++)
scanf("%d%d", &range[i].l, &range[i].r);
sort(range, range + n);
int res = 0;
bool flag = false;
for(int i = 0; i < n; i ++){
int j = i, r = -2e9;
while(j < n && range[j].l <= st){
r = max(r, range[j].r);
j ++;
}
if(r < st){
res = -1;
break;
}
res ++;
if(r >= ed){
flag = true;
break;
}
st = r;
i = j - 1;
}
if(!flag)
res = -1;
cout << res << endl;
return 0;
}
Huffman 树
合并果子
知识点
解题思路
- 可以看到上图中的数中的叶子结点都是给定的果子,中间节点都是合并之后的果子
- 最后消耗的总体力值为 $3a+3b+2c+2d+2e$
- 以上问题可以转化为哈夫曼问题
实现代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
priority_queue<int, vector<int>, greater<int>> heap;
for(int i = 0;i < n; i ++){
int x;
scanf("%d", &x);
heap.push(x);
}
int res = 0;
while(heap.size() > 1){
int a = heap.top();
heap.pop();
int b = heap.top();
heap.pop();
res += a + b;
heap.push(a + b);
}
cout << res << endl;
return 0;
}
排序不等式
排队打水
知识点
- 一定要注意:如果使用
while(n --)
进行循环,那么之后n
就已经为0了,如果后续还要使用到n
,那么一定不可以使用while(n --)
解题思路
- 按照从小到大进行排序,总时间最小
- 数学证明:
- 假设我们所有元素不是从小到大进行排序的,然后存在一个最优序列
- 那么就必然存在两个等待时间前一个大于后一个,即:\(t_i > t_{i+1}\)
- 则对于该区间的等待时间为\(T_{wait} = (n-1)\times t_1 +\dots+(n-i)\times t_i+(n-i-1)\times t_{i+1} + \dots+ t_{n-1}\)
- 如果我们交换$t_i,t_{i+1}$,那么等待时间将变为\(T'_{wait} = (n-1)\times t_1 +\dots+(n-i)\times t_{i+1}+(n-i-1)\times t_i + \dots+ t_{n-1}\)
- 则可以发现,等待时间变小了\(T_{wait} - T'_{wait}=t_i-t_{i+1} > 0\)
- 与假设矛盾
- 所以所有元素必须从小到大进行排列,才能得到最优子序列
实现代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
priority_queue<int ,vector<int>, greater<int>> q;
for(int i = 0; i < n; i ++){
int x;
scanf("%d", &x);
q.push(x);
}
long long res = 0;
for(int i = 0; i < n - 1; i ++){
int t = q.top();
q.pop();
res += t * (n - 1 - i);
}
cout << res << endl;
}
绝对值不等式
知识点
- 绝对值不等式:
- 有一个区间$[a,b]$,当$x$位于$[a,b]$中时
解题思路
- 将所有位置进行排序
- 排序后第一个位置和最后一个位置两两成组,在组成的区间内选点
- 这样的就满足绝对值不等式
实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
int a[N];
int n;
int main(){
cin >> n;
for(int i = 0; i < n; i ++)
scanf("%d", &a[i]);
sort(a, a + n);
int res = 0;
for(int i = 0; i < n; i ++)
res += abs(a[i] - a[n / 2]);
cout << res << endl;
return 0;
}
推公式
知识点
解题思路
- 每头牛的危险值计算公式为\(\text{Risk}=\sum^{\text{前面的牛}}w - s\)
- 要是每头牛的危险值越小,可以考虑:
- 体重越大的牛放在最底部
- 强壮系数越大的牛放在最底部
- 可以考虑将$w + s$越大的牛放在越底部
- 数学证明:每头牛的危险系数如下表所示
cow | before exchange | after exchange |
i | $\sum^{i-1}_{j=1}w_j-s_i$ | $\sum^{i-1} {j=1}w_j+w {i+1}-s_i$ |
i+1 | $\sum^{i}{j=1}w_j-s{i+1}$ | $\sum^{i-1}{j=1}w_j-s{i+1}$ |
- 对每个方格减去$\sum^{i-1}_{j=1}w_j$
cow | before exchange | after exchange |
i | $-s_i$ | $w_{i+1}-s_i$ |
i+1 | $w_i-s_{i+1}$ | $-s_{i+1}$ |
- 接下来我们要比较交换前和交换后的的最大值,即比较max($-s_i$, $w_i-s_{i+1}$)和max($w_{i+1}-s_i$, $-s_{i+1}$)
- 当$-s_i>w_i-s_{i+1}$时
- max($-s_i$, $w_i-s_{i+1}$) = $-s_i$
- 若右侧的值为$w_{i+1}-s_i$,则右侧一定大于
实现代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 50000;
int n;
PII cow[N];
int main(){
cin >> n;
for(int i = 0; i < n; i ++){
int w, s;
cin >> w >> s;
cow[i] = {w + s, w};
}
sort(cow, cow + n);
int res = -2e9, sum = 0;
for(int i = 0; i < n; i ++){
int s = cow[i].first - cow[i].second, w = cow[i].second;
res = max(res, sum - s);
sum += w;
}
cout << res << endl;
return 0;
}
Enjoy Reading This Article?
Here are some more articles you might like to read next:
Google Gemini updates: Flash 1.5, Gemma 2 and Project Astra Displaying External Posts on Your al-folio Blog 强化学习导论 企业项目实训 面试总结