学习yxc算法基础课记录,方便复习
贪心问题的应对思路:先从直觉上猜一个做法,然后设法验证
区间问题
- 区间问题大部分需要排序(将数据变成“单峰”的排布)
- 证明贪心问题的常用策略
- a >= b && a <= b <=> a == b
- 反证法
- 将每个区间按照右端点从小到大排序
- 如果当前区间已经包含点,直接pass;否则选择当前区间最右边的点
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
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 ++ ) {
int l, r;
scanf("%d%d", &l, &r);
range[i] = {l, 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;
}
思路解法同“区间选点”问题
- 将所有区间按照左端点从小到大排序
- 从前往后处理每一个区间,判断能否将其放到某一个区间当中(l[i] > max_r)
如果不存在这样的组,则为当前区间开一个新组
否则,将当前区间放到该组之中,并更新该组的max_r
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l, r;
bool operator< (const Range &w)const {
return l < w.l;
}
}range[N];
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i ++ ) {
int l, r;
scanf("%d%d", &l, &r);
range[i] = {l, r};
}
sort(range, range + n);
priority_queue<int, vector<int>, greater<int>> heap;
for(int i = 0; i < n; i ++ ) {
auto r = range[i];
if(heap.empty() || heap.top() >= range[i].l) heap.push(r.r);
else {
heap.pop();
heap.push(r.r);//由于heap.top() < range[i].l,一定有heap.top()<range[i].
}
}
printf("%d", heap.size());
return 0;
}
- 将左端点从小到大排序
- 从前完后依次枚举每个区间,在所有能覆盖start的区间中,选择一个右端点最大的区间,将start更新成右端点的最大值
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range &w) const
{
return l < w.l;
}
}range[N];
int main()
{
int start, end;
scanf("%d%d", &start, &end);
scanf("%d", &n);
for(int i = 0; i < n; i ++ )
{
int l, r;
scanf("%d%d", &l, &r);
range[i] = {l, r};
}
sort(range, range + n);
bool success = false;
int res = 0;
for(int i = 0; i < n; i ++ )
{
int j = i, r = -2e9; //双指针扫描,j是扫描探头,r是记录头
while(j < n && range[j].l <= start)
{
r = max(r, range[j].r);
j ++ ;
}//找到能覆盖start的区间向右所能达到的最远距离
if(r < start)
break;//所有区间都在start左边
res ++;
if(r >= end)
{
success = true;
break;
}//找到了终结者
start = r;
i = j - 1;//探头向前多走了一步
}
if(!success) res = -1;
printf("%d", res);
return 0;
}
Huffman树
- 整个合并过程可以模拟为一棵Huffman树
- 每次选出权值最小的两堆果子进行合并
- 证明思路:
证明任何时候权值最小的两个点在树中的深度最深并且可以互为兄弟(通过交换模拟得到这种情况后,整棵树的累积权值一定会降低)
第n次选当前最优选择不会对第n + 1次选到最优选择产生影响
/*
分析:
合并的过程可以用一个完全二叉树模拟;
因为越靠近叶子节点的位置被操作的次数越多,所以应该优先操作对权重贡献最小的元素;
类比现实生活中面对类似情况时也会本能地这么做,用编码模拟这个过程;
最小的两个点,在完全二叉树中一定最深,且可以互为兄弟;
Huffman树
*/
#include <iostream>
#include <queue>
using namespace std;
int main()
{
int n;
scanf("%d", &n);
priority_queue<int, vector<int>, greater<int>> heap;
while(n -- )
{
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);
}
printf("%d
", res);
return 0;
}
排序不等式
- 按照从小到大排序,总时间一定最小
- 反证法可以证明
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int t[N];
int main() {
cin >> n;
for(int i = 0; i < n ; i ++ ) cin >> t[i];
sort(t, t + n);
LL res = 0;
for(int i = 0; i < n; i ++) res += t[i] * (n - i - 1);
cout << res << endl;
return 0;
}
绝对值不等式
- 将距离两两分组,当各组同时取到最小值时,整体的距离也最小
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int main() {
cin >> n;
for(int i = 0 ; i < n ; i ++ ) cin >> 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;
}
推公式
- 将所有牛的“重量+力量”从小到大排序即可以得到最优解(最大的危险系数最小)
- 反证法证明:假设最优解局部存在逆序,通过对逆序的位置进行调整得到的结果,其危险系数更优,与假设矛盾
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 50010;
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 w = cow[i].second, s = cow[i].first - w;
res = max(res, sum - s);
sum += w;
}
cout << res << endl;
return 0;
}