前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员进阶之算法练习(三十九)Codeforces

程序员进阶之算法练习(三十九)Codeforces

作者头像
落影
发布2020-04-09 10:57:46
4350
发布2020-04-09 10:57:46
举报
文章被收录于专栏:落影的专栏落影的专栏

正文

1.Drinks Choosing

题目链接 题目大意: 有n个学生,每个学生都有自己喜欢的饮料类型,用整数?1,?2,…,?? (1≤??≤?)表示,一共有k种饮料的类型。 现在老师要采购饮料,饮料是两瓶装; 所以老师打算采购(n/2)个单位,保证每个学生至少有一瓶。(n/2向上取整,如果5个人,老师会买3个单位) 老师希望尽可能多的学生能喝到喜欢的饮料类型,问最多能有几个学生喝到自己喜欢的饮料?

输入: 第一行,? and ? (1≤?,?≤1000) 接下来n行,分别表示 ?1,?2,…,?? (1≤??≤?)

输出: 能够喝到喜欢饮料的学生人数;

Examples input 5 3 1 3 1 1 2 output 4

题目解析: 兴趣相同的,两两成对拿出来,凑成一个单位;(ans += 2) 剩下的所有人(n - ans),每个人的兴趣都不相同,任意两两凑对一个单位;(n-ans+1)/2

代码语言:javascript
复制
    int n, k;
    cin >> n >> k;
    int a[1001] = {0}, ans = 0;
    for (int i = 0; i < n; ++i) {
        int t;
        cin >> t;
        ++a[t];
        if (a[t] % 2 == 0) {
            ans += 2;
        }
    }
    ans += (n - ans + 1) / 2;
    cout << ans << endl;
2.Sport Mafia

题目链接 题目大意: 小明有个糖果盒子,起始的时候是空的。 小明会进行n次操作,每次可以选择: 1、吃掉盒子里的一个糖果;(如果里面有糖果的话) 2、放进去x个糖果,之后x=x+1;(比如这次是放5个,下次就是放6个) 最后盒子里会剩下k个糖果;

例如下面的9个操作: put 1 candy, put 2 candies, eat a candy, eat a candy, put 3 candies, eat a candy, put 4 candies, eat a candy, put 5 candies.

最终会剩下11个糖果。(1+2−1−1+3−1+4−1+5=11)

现在给出操作次数n,还有最终剩下的k个糖果,问小明会吃掉几个糖果。

输入: 第一行,? and ? (1≤?≤10^9; 0≤?≤10^9)

输出: 小明吃掉的糖果数;(题目保证数据是有解的)

Examples input 5 3 1 3 1 1 2 output 4

题目解析: 由题目知道,吃掉的糖果是1、2、3、4、、、,那么假设吃掉的是1~t的糖果。 那么剩下的(n-t)次糖果,肯定是吃糖果的操作。 如果题目有解,那么就有: 总共的放进去糖果数 = 吃糖果数 + 剩下糖果数; 即是:(1+t)*t/2 = (n-t) + k

可以从1开始遍历t,最多重复sqrt(10^9)次就会有解,复杂度可以接受。

代码语言:javascript
复制
    int n, k;
    cin >> n >> k;
    for (int i = 1; i < 100000; ++i) {
        lld sum = (1ll + i) * i / 2;
        if (sum == (k + (n - i))) {
            cout << sum - k << endl;
            return 0;
        }
    }
3.Basketball Exercise

题目链接 题目大意: 篮球教练有2 * n个学生,排成两行,每行有n个人;

每个学生都有一个高度h;(1≤h≤10e9)

现在教练需要选择若干个学生去参加篮球比赛,他决定从左到右选择学生,并且:

1、每列只选择一个学生;

2、不连续选择两个同一行的学生,如果这次选择了第一行的学生,则下次必然选择第二行的学生;

问选择出来的学生高度和最大值是多少;

输入: 第一行,? (1≤?≤10e5) 第二行,n个整数h,表示第一行学生高度 (1≤ℎ≤10e9) 第三行,n个整数h,表示第二行学生高度 (1≤ℎ≤10e9)

输出: 选择出来的学生高度总和最大值;

Examples input 5 9 3 5 7 3 5 8 1 4 5 output 29

图中灰色为选中学生

input 3 1 2 9 10 1 1 output 19

图中灰色为选中学生

题目解析: 两个数组,从左到右遍历选择数字,每个index只能选择一个数字,并且两个数组要交替选择。

对于每个数字,只有两种选择:选中或者不选中; 对于每个index,则有三种状态:选择数组a的元素(状态1)、选择数组b的元素(状态2)、都不选择(状态0); 那么有dp[N][i]: dp[i][0] = max(dp[i-1][0], dp[i-1][1], dp[i-1][2]); dp[i][1] = max(dp[i-1][2], dp[i-1][0]) + a[i]; dp[i][2] = max(dp[i-1][1], dp[i-1][0]) + b[i];

然后选择dp[N][i]中的最大值即可。

代码语言:javascript
复制
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    for (int i = 0; i < n; ++i) {
        cin >> b[i];
    }
    
    lld ans = max(a[0], b[0]);
    dp[0][0] = 0;
    dp[0][1] = a[0];
    dp[0][2] = b[0];
    
    for (int i = 1; i < n; ++i) {
        dp[i][0] = max(max(dp[i-1][0], dp[i-1][1]), dp[i-1][2]);
        dp[i][1] = max(dp[i-1][2], dp[i-1][0]) + a[i];
        dp[i][2] = max(dp[i-1][1], dp[i-1][0]) + b[i];
        for (int j = 0; j < 3; ++j) {
            ans = max(ans, dp[i][j]);
        }
    }
    
    cout << ans << endl;
4.Letters Shop

题目链接 题目大意: 有一个小写字母组成的字符串s,长度为n; 有m个人,每个人有一个名字,假如是t[i]; 现在小明想知道,对于每个人,至少需要s的前面多少个字符,才能组成他的名字;

假如 ? ="arrayhead",?[?]="arya",那么需要前面5个字符array,才能够组成arya的名字; 假如 ? ="arrayhead",?[?]="areahydra",那么需要前面9个字符arrayhead,才能组成areahydra的名字;

输入: 第一行,整数?,表示字符串长度 (1≤?≤2⋅10^5) 第二行,字符串s; 第三行,整数m,表示m个人; (1≤?≤5⋅10^4) 接下来m行,每行有一个字符串t[i]; (1≤|?[?]|≤2⋅10^5) 题目保证每个人的名字,都可以由字符串s组成,并且m个人的名字总长度不会超过2⋅10^5。

输出: m行,每行有一个数字,表示需要的最少字符数量。

题目解析: 先不考虑复杂度,直接的做法是将每个人的名字拿出来匹配,一共匹配m次; 怎么匹配比较方便? 把名字统计下,得到b[26],表示26个字符的数量; 然后遍历整个字符串,直到26个字母的数量都满足; 复杂度是O(N),总的复杂度是O(NM);

这个复杂度太高,需要优化。 容易知道,如果前i个字符满足要求,则前i+1个字符也满足要求,那么就可以二分。 同时为了避免多次计算,可以直接换成a[i][j]表示前i个字符,第j个字母的数量。

代码语言:javascript
复制
    int n;
    cin >> n;
    string str;
    cin >> str;
    a[0][str[0] - 'a'] = 1;
    
    for (int i = 1; i < n; ++i) {
        for (int j = 0; j < 26; ++j) {
            a[i][j] = a[i - 1][j];
        }
        ++a[i][str[i] - 'a'];
    }
    
    int m;
    cin >> m;
    while (m--) {
        string t;
        cin >> t;
        int cnt[26] = {0};
        for (int i = 0; i < t.length(); ++i) {
            ++cnt[t[i] - 'a'];
        }
        
        int l = 0, r = n - 1, ans = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            
            int ok = 1;
            for (int i = 0; i < 26; ++i) {
                if (cnt[i] && a[mid][i] < cnt[i]) {
                    ok = 0;
                }
            }
            
            if (ok) {
                ans = mid;
                r = mid - 1;
            }
            else {
                l = mid + 1;
            }
        }
        
        cout << ans + 1 << endl;
    }

总结

题目1是贪心,也没有特别的trick; 题目2提供的解法是暴力去枚举,如果操作的次数比较多,比如说n=10e18,此时放入糖果的操作次数会比较多,此时可以使用二分查找;(判断条件是糖果有没有剩余) 题目3是动态规划,状态转移比较简单;样例的数据有点像LIS(最长上升子序列),一开始理解错题意,以为是要求选择出来的人是要身高递减,但是这个题目又不能按照LIS一样做到O(NlogN); 题目4就是典型的二分题目。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文
    • 1.Drinks Choosing
      • 2.Sport Mafia
        • 3.Basketball Exercise
          • 4.Letters Shop
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档