【算法学习笔记】51. 区间排序问题 SJTU OJ 1360 偶像丁姐的烦恼

Description

成为LL冠军的人气偶像丁姐最近比较烦,许多商业活动找上门来。因为每次商业活动给的毛爷爷都一样,所以丁姐希望能够尽可能多的参加这些活动。然而,商业活动的起止时间并不由丁姐说了算,因此丁姐想写一个程序,求出他最多能够参加的商业活动的数量。

Input Format

第一行一个数n,表示可选活动的数量。

接下n行每行两个数,表示每个活动开始时间t1_i和结束的时间t2_i。

Output Format

一个数字,表示丁姐最多能够参加的活动的数量。

Sample Input

10
0 3
0 5
10 13
12 15
2 6
4 8
9 11
13 18
14 16
15 20

Sample Output

5

Hint

样例选取的活动时间为:(0, 3), (4, 8), (9, 11), (12, 15), (15, 20)

n≤100000

0≤t1_i<t2_i≤1000000

思路1.按左端排序 (同左则按右)

错误代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

struct Period
{
    int start;
    int end;
};

//按照start排序
int cmp_period(const void* _a, const void* _b){
    Period* a = (Period*) _a;
    Period* b = (Period*) _b;
    if((*a).start != (*b).start)
        return (*a).start - (*b).start;
       else
        return (*a).end - (*b).end;
}

Period ps[100001];

int main(int argc, char const *argv[])
{

    int n;
    cin>>n;
    for (int i = 0; i < n; ++i){

        cin>>ps[i].start>>ps[i].end;
    }

    qsort(ps,n,sizeof(Period),cmp_period);

    // for (int i = 0; i < n; ++i)
    // {
    //     cout<<ps[i].start<<" "<<ps[i].end<<endl;
    // }

    int cur = ps[0].start;
    int index = 0;
    int ans = 1;
    while(1){
        cur = ps[index].end;
        index++;
        for(;index < n; index++){
            if(ps[index].start >= cur){
                if(index<n-1 and ps[index+1].end < ps[index].end)
                    index++;
                ans++;
                break;
            }
        }
        if(index >= n-1)
            break;
    }
    cout<<ans<<endl;
    return 0;
}

错误代码1

样例经过排序后则是

0 3

0 5

2 6

4 8

9 11

10 13

12 15

13 18

14 16

15 20

第一直觉是先选择第一个区间,然后选择左端点大于等于第一个区间右端点的第一个区间,(4,8) 然后以此类推。因为同样的起点里,肯定选择的是尾部更小的。

但是这种方法会产生一个问题就是,遇到了完全重合区间没有办法延伸。

比如

0 3

3 9

4 8

8 9

按刚才的算法回选择 0 3, 3 9 但是实际上应该是 0 3, 4 8, 8 9

原因在于4,8是完全含于3,9的,所以我们要选择4,8并继续进行下去

所以要在这个基础上进行修改。

(另,可以暂时放弃跳过所有相同头部的这个想法。。。)

注意 核心计算的部分应该是个在线算法(O(n))这也暗示着无论何时停止都应该得到当前的结果

先拿到0,3

再更新为 3 9

2个

此时又来了一个4 8 我们要去看4 8 与 3 9的关系

首先4>=3是一定的 因为排序过,所以只要确定两个结尾的大小即可。

发现如果新元素的尾部小于旧元素的尾部,可以把3 9 删去 换为 4 8

因为删一个 增一个 所以ans不变,但是尾部要更新为8

所以只需多维护一个变量 cur 即可 表示当前结果的尾部的数字。(其实这点就是在暗示,按尾部排序更方便,这个维护的过程其实就是排序尾部的过程。)

正确代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

struct Period
{
    int start;
    int end;
};

//按照起始点排序
int cmp_period(const void* _a, const void* _b){
    Period* a = (Period*) _a;
    Period* b = (Period*) _b;
    if((*a).start != (*b).start)
        return (*a).start - (*b).start;
       else
        return (*a).end - (*b).end;
}

Period ps[100001];

int main(int argc, char const *argv[])
{

    int n;
    cin>>n;
    for (int i = 0; i < n; ++i){

        cin>>ps[i].start>>ps[i].end;
    }

    qsort(ps,n,sizeof(Period),cmp_period);

    int cur = ps[0].end;
    int ans = 1;
    for (int i = 1; i < n; ++i)
    {
        if(ps[i].start >= cur){
            cur = ps[i].end;
            ans++;
        }
        if(ps[i].end < cur)
            cur = ps[i].end;
    }
    cout<<ans<<endl;
    return 0;
}
 

思路1正确代码

思路2:按右端排序,直接遍历选举所有开始时间大于当前终止时间的节点,不断维护更新终止时间即可。

用枪哥的话说,这其实是个贪心的策略,越早结束说明可以越早的参加其他的活动,所以要对尾端进行排序。

代码很简单,如下:

#include <iostream>
#include <algorithm>
using namespace std;

struct Period
{
    int start;
    int end;
};

//按照end排序
int cmp_period(const void* _a, const void* _b){
    Period* a = (Period*) _a;
    Period* b = (Period*) _b;
    //if((*a).end != (*b).end)
        return (*a).end - (*b).end;
       //else
     //   return (*a).start - (*b).start;
}

Period ps[100001];

int main(int argc, char const *argv[])
{

    int n;
    cin>>n;
    for (int i = 0; i < n; ++i){

        cin>>ps[i].start>>ps[i].end;
    }

    qsort(ps,n,sizeof(Period),cmp_period);
    int ans = 1;
    int cur = ps[0].end;
    for (int i = 1; i < n; ++i)
    {
        if(ps[i].start >= cur){
            cur = ps[i].end;
            ans++;
        }
    }

    cout<<ans<<endl;
    return 0;
}

/*
10
0 3
0 5
10 13
12 15
2 6
4 8
9 11
13 18
14 16
15 20 

*/

思路2正确代码

时间: 2024-10-13 01:02:22

【算法学习笔记】51. 区间排序问题 SJTU OJ 1360 偶像丁姐的烦恼的相关文章

【算法学习笔记】63. BFS SJTU OJ 1281 蹦蹦跳跳

典型的BFS,找到起点直接进行搜搜即可.要注意的就是层数的处理.坐标到id的转换.还有就是要尽早判断是否达到终点. 代码注释很详细,最后面两个函数是一开始写的 用抽取邻接矩阵+Dijkstra 来算的,很麻烦 头脑一热的结果.. #include <vector> #include <queue> #include <iostream> using namespace std; int map[31][31]={0}; int M,N,M1,M2; struct Poi

【算法学习笔记】31.动态规划 SJTU OJ 1320 numtri

Description Consider the number triangle shown below. Write a program that calculates the highest sum of numbers that can be passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left

【算法学习笔记】68.枚举 SJTU OJ 1272 写数游戏

很简单 不用太考虑效率 虽然每次都要重新排序 注意vector的使用,非常便利. 还有一个技巧就是用一个have型bool数组来记录是否存在. #include <iostream> #include <vector> #include <algorithm> using namespace std; bool have[1000] = {0}; vector<int> v; //用vector来实现动态数组的简便性 bool cmp_int(const i

算法学习笔记 递归之 快速幂、斐波那契矩阵加速

递归的定义 原文地址为:http://blog.csdn.net/thisinnocence 递归和迭代是编程中最为常用的基本技巧,而且递归常常比迭代更为简洁和强大.它的定义就是:直接或间接调用自身.经典问题有:幂运算.阶乘.组合数.斐波那契数列.汉诺塔等.其算法思想: 原问题可分解子问题(必要条件): 原与分解后的子问题相似(递归方程): 分解次数有限(子问题有穷): 最终问题可直接解决(递归边界): 对于递归的应用与优化,直接递归时要预估时空复杂度,以免出现用时过长或者栈溢出.优化递归就是以

EM算法学习笔记2:深入理解

文章<EM算法学习笔记1:简介>中介绍了EM算法的主要思路和流程,我们知道EM算法通过迭代的方法,最后得到最大似然问题的一个局部最优解.本文介绍标准EM算法背后的原理. 我们有样本集X,隐变量Z,模型参数θ,注意他们3个都是向量,要求解的log似然函数是lnp(X|θ),而这个log似然函数难以求解,我们假设隐变量Z已知,发现lnp(X,Z|θ) 的最大似然容易求解. 有一天,人们发现引入任意一个关于隐变量的分布q(Z),对于这个log似然函数,存在这样一个分解: lnp(X|θ)=L(q,θ

算法学习笔记 KMP算法之 next 数组详解

最近回顾了下字符串匹配 KMP 算法,相对于朴素匹配算法,KMP算法核心改进就在于:待匹配串指针 i 不发生回溯,模式串指针 j 跳转到 next[j],即变为了 j = next[j]. 由此时间复杂度由朴素匹配的 O(m*n) 降到了 O(m+n), 其中模式串长度 m, 待匹配文本串长 n. 其中,比较难理解的地方就是 next 数组的求法.next 数组的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀,也可看作有限状态自动机的状态,而且从自动机的角度反而更容易推导一些. "前

算法学习笔记 最短路

图论中一个经典问题就是求最短路,最为基础和最为经典的算法莫过于 Dijkstra 和 Floyd 算法,一个是贪心算法,一个是动态规划,这也是算法中的两大经典代表.用一个简单图在纸上一步一步演算,也是很好理解的,理解透自己多默写几次即可记住,机试时主要的工作往往就是快速构造邻接矩阵了. 对于平时的练习,一个很厉害的 ACMer  @BenLin_BLY 说:"刷水题可以加快我们编程的速度,做经典则可以让我们触类旁通,初期如果遇见很多编不出,不妨就写伪代码,理思路,在纸上进行整体分析和一步步的演算

[算法学习笔记]直接插入排序笔记

直接插入排序概念: 带排元素放在elem[0...n-1]中,初始化时,elem[0]自成1个有序区,无序区为elem[1...n-1],从i=1起,到i=n-1,依次将elem[i]插入有序区[0...n-1]中 直接插入排序算法步骤: 1.在当前有序区域R[1,i-1]中查找R[i]的正确插入位置K(1<=K<=i-1) 2.将R[K,i-1]中的记录均向后移动 3.移动后腾出K位置,插入R[i] (最坏)时间复杂度:O(n^2) 空间复杂度:O(1) /// <summary>

八大排序算法学习笔记:冒泡排序

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法. 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成.这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端. 算法原理: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 针对所有