• 刷题笔记-枚举与模拟


    连号区间

    思路:

    1.暴力思路:枚举起始、终止位置;对子区间排序;依次判断相邻位置是否差1.

    • 时间复杂度(O(n^3log^n)),显然需要优化
      2.优化
    • 性质:子序列中max - min == r - l

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 10010;
    int a[N];
    int n;
    int main(void)
    {
        cin >> n;
        for(int i = 0; i < n;i++)
            cin >> a[i];
        
        //性质:最大值 - 最小值 == 数的个数 - 1
        int ans = 0;
        for(int i = 0; i < n; i++)
        {
            int maxv = -N, minv = N;
            for(int j = i; j < n; j++)
            {
                maxv = max(maxv, a[j]);
                minv = min(minv, a[j]);
                if(maxv - minv == j - i)  ans++;
            }
        }
        cout << ans << endl;
        
        return 0;      
    }
    

    递增三元组

    思路:

    1.暴力:三重循环找出对于每一个A,满足条件的B的个数,对于每一个B,满足条件的C个个数,然后相乘。

    • 时间复杂度(O(n^3)),需要优化
      2.本题最关键的是因为枚举A或C,则其余两个都会有联系,而枚举B,则A、C之间没有联系,可以使用乘法原理
    • 枚举每一个B,找出满足条件的A、C,然后相乘
    • 时间复杂度(O(n^2)),还是会超时,需要优化到(O(n))(O(nlog^n))
      3.排序+二分 (O(nlog^n))
    • 对A、C进行排序,然后使用二分法,找出满足条件的A、C
      4.前缀和-mark-(O(n))
      -使用数组cnt分别统计A、C中每一个数字出现的次数
      -计算数组cnt的前缀和数组s,则s[i]表示小于i的所有数的个数

    代码1-二分:

    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    typedef long long LL;
    
    const int N = 100010;
    
    int a[N],b[N],c[N];
    LL ans,res;
    int main(void)
    {
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++)    scanf("%d", &a[i]), a[i]++;
        for(int i = 1; i <= n; i++)    scanf("%d", &b[i]), b[i]++;
        for(int i = 1; i <= n; i++)    scanf("%d", &c[i]), c[i]++;    
        //  排序 + 二分     
        sort(a + 1,a + n + 1);
        sort(c + 1,c + n + 1);
        for(int i = 1; i <= n; i++)
        {
            int l = 1, r = n;
            //寻找a[l] < b[i]
            while(l < r)
            {
                int mid = l + r + 1 >> 1;
                if(a[mid] < b[i])
                    l = mid;
                else
                    r =  mid - 1;
            }
            int anum = l;
            //边界判断
            if(b[i] <= a[1])
                anum = 0;
            
            //寻找c[l] > b[i]
            l = 1; r = n;
            while(l < r)
            {
                int mid = l + r >> 1;
                if(c[mid] > b[i])
                    r = mid;
                else
                    l =  mid + 1;
            }
            int cnum = n - l + 1;
            //边界判断
            if(b[i] >= c[n])
                cnum = 0;
            res += (LL)anum * cnum;  
        }
        cout << res << endl;  
        return 0;     
    }
    

    代码2 -前缀和

    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    typedef long long LL;
    
    const int N = 100010;
    
    int a[N],b[N],c[N];
    int cnt[N],as[N],cs[N];
    LL ans,res;
    int main(void)
    {
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++)    scanf("%d", &a[i]), a[i]++;
        for(int i = 1; i <= n; i++)    scanf("%d", &b[i]), b[i]++;
        for(int i = 1; i <= n; i++)    scanf("%d", &c[i]), c[i]++;
      
        //前缀和
        for(int i = 1; i <= n; i++)   cnt[a[i]]++;
        for(int i = 1; i < N; i++)   as[i] = as[i-1] + cnt[i];
        
        memset(cnt,0, sizeof(cnt));
        for(int i = 1; i <= n; i++)   cnt[c[i]]++;
        for(int i = 1; i < N; i++)   cs[i] = cs[i-1] + cnt[i];
        
        for(int i = 1; i <= n; i++)
            ans +=   (LL)as[b[i]-1] * (cs[N-1] - cs[b[i]]);     //如果不加强制类型转换(LL),答案会错误
          
        cout << ans << endl;
        return 0;      
    }
    

    强调:
    res += (LL)anum * cnum;
    如果不进行强制类型转化,那么在乘积爆int的情况下就会计算出错

    回文日期

    在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。
    牛牛习惯用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。
    显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。
    牛牛认为,一个日期是回文的,当且仅当表示这个日期的8位数字是回文的。
    现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。
    一个 8 位数字是回文的,当且仅当对于所有的 i(1≤i≤8) 从左向右数的第i个数字和第 9−i 个数字(即从右向左数的第 i 个数字)是相同的。
    例如:
    •对于2016年11月19日,用 8 位数字 20161119 表示,它不是回文的。
    •对于2010年1月2日,用 8 位数字 20100102 表示,它是回文的。
    •对于2010年10月2日,用 8 位数字 20101002 表示,它不是回文的。

    输入格式

    输入包括两行,每行包括一个8位数字。
    第一行表示牛牛指定的起始日期date1,第二行表示牛牛指定的终止日期date2。保证date1和date2都是真实存在的日期,且年份部分一定为4位数字,且首位数字不为0。
    保证date1一定不晚于date2。

    输出格式

    输出共一行,包含一个整数,表示在date1和date2之间,有多少个日期是回文的。

    思路:

    1.对于日期问题,一般的做法是:

    • 枚举范围内的所有符合条件的数字
    • 判断是不是一个日期,判断是否满足每个月份的天数要求

    代码:

    #include <cstring>
    #include <cstdio>
    #include <iostream>
    
    using namespace std;
    
    int days[13] = {0, 31 , 28, 31, 30 , 31, 30, 31, 31 , 30, 31, 30,  31};
    int date1,date2;
    bool check(int date)
    {
        int year = date / 10000;
        int mouth = date / 100 % 100;
        int day = date % 100;
        
        if(mouth == 0 || mouth > 12) return false;
        if(day == 0 || mouth != 2 && day > days[mouth]) return false;
        //判断闰年 + 特判二月
        int is = year % 100 && year % 4 == 0 || year % 400 == 0;
        if(day > days[mouth] + is)  return false;
        
        return true;
    }
    int main(void)
    {
        cin >> date1 >> date2;
        int ans = 0;
        //枚举回文数
        for(int i = 1000; i < 9999; i++)
        {
            int date = i, x = i;
            for(int j = 0; j < 4;j++) date = date * 10 + x % 10, x /= 10;
            //判断是否符合日期范围,是否是合法日期
            if(date >= date1 && date <= date2 && check(date)) ans++;
        }
        
        cout << ans << endl;
        
        return 0;
    }
    

    日期问题

    小明正在整理一批历史文献。这些历史文献中出现了很多日期。
    小明知道这些日期都在1960年1月1日至2059年12月31日。
    令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。
    更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。
    比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。
    给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

    输入格式

    一个日期,格式是”AA/BB/CC”。
    即每个’/’隔开的部分由两个 0-9 之间的数字(不一定相同)组成。

    输出格式

    输出若干个不相同的日期,每个日期一行,格式是”yyyy-MM-dd”。
    多个日期按从早到晚排列。

    数据范围

    (0≤A,B,C≤9)

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    int days[13] = {0, 31 , 28, 31, 30 , 31, 30, 31, 31 , 30, 31, 30,  31};
    
    bool check(int year ,int mouth, int day)
    {
        if(mouth > 12 ||mouth == 0 || day > 31 || day == 0)  return false;
        if(mouth != 2 && day > days[mouth]) return false;
        int is = year % 100 && year % 4 == 0 || year % 400 == 0;
        if(mouth == 2 && day > days[mouth] + is) return false;
        
        return true;
    }
    int main(void)
    {
        int a,b,c;
        scanf("%d/%d/%d",&a,&b,&c);      //使用scanf可以规定读入格式
        
        for(int date = 19600101 ; date <= 20591231; date ++)
        {
            int year = date / 10000, mouth = (date / 100) % 100, day = date % 100;
            if(check(year, mouth, day))
                if(year % 100 == a && mouth == b && day == c ||          
                   year % 100 == c && mouth == a && day == b ||
                   year % 100 == c && mouth == b && day == a)
                   printf("%d-%02d-%02d
    ", year, mouth, day);       //以两位输出整数,如果不足两位,则使用前导0补充
        }
        
        return 0;
    }
    

    注意:
    使用scanf和printf模拟输入和输出格式

    外卖店的优先级

    思路:

    1.暴力:对时间遍历,枚举对应时间的订单,每一次都需要对所有店铺操作,显然超时
    2.优化:最费时的部分是对没有订单的店铺-1;

    • 所以可以通过排序,将同一家店铺的订单按照时间排序,这样在两次订单时间间隔-1,可以通过一次操作完成。

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    typedef pair<int, int> PII;
    
    const int N = 100010;
    int cnt[N],last[N];
    bool st[N];
    PII dat[N];
    int main(void)
    {
        int n,m,t;
        cin >> n >> m >> t;
        for(int i = 0; i < m;i++)
            scanf("%d%d", &dat[i].first, &dat[i].second); //id + t
        //sort对pair,默认按照第一个元素升序,相同按照第二个元素升序
        sort(dat, dat + m);   
    
        for(int i = 0; i < m;)
        {
            int j = i;
            while(j < m && dat[i] == dat[j]) j++;             //统计id  t都相同的个数
            int tim = dat[i].first, id = dat[i].second, num = j - i;
            i = j;
            
            cnt[id] -= tim - last[id] - 1;
            if (cnt[id] < 0)   cnt[id] = 0;
            if (cnt[id] <= 3)  st[id] = false; 
    
            cnt[id] += num * 2;
            if (cnt[id] > 5) st[id] = true;
    
            last[id] = tim;
        }
        //从最后一次订单到t时刻的降级
        for(int i = 1; i <= n; i++)
        {
     
            cnt[i] -= t - last[i];
            if (cnt[i] <= 3) st[i] = false;
             
        }               
                
        int ans = 0;
        for(int i = 1; i <= n;i++)
            if(st[i]) 
                ans++;
        cout << ans << endl;
        
        return 0;
    }
    
  • 相关阅读:
    一个简单的爬虫case2
    一个简单的爬虫case1
    Kick Start 2018-Round H-Problem C. Let Me Count The Ways
    Kick Start 2018-Round H-Problem B. Mural
    Kick Start 2018-Round H-Problem A. Big Buttons
    211. Add and Search Word
    HDU-1506 Largest Rectangle in a Histogram
    HDU-1236 排名
    HDU-1009 FatMouse' Trade
    HDU-1231 最大连续子序列
  • 原文地址:https://www.cnblogs.com/zy200128/p/12631829.html
Copyright © 2020-2023  润新知