牛客小白月赛115

 赛时成绩如下:

A. 过马路

题目链接:A-过马路_牛客小白月赛115

题目描述

红绿灯有 "红“,"黄","绿",三种颜色。红灯表示禁止通行,绿灯表示准许通行,黄灯表示警示。
在小 E 的世界中,红灯后要变为绿灯,绿灯后要变为红灯,不允许连续出现相同颜色的灯。同时红绿灯每次颜色在红绿间切换的间隔都要先变为黄灯作为警示。
现在小 E 用三个整数描述了某路口红绿灯的变化,其中 −1 表示红灯, 0 表示黄灯, 1 表示绿灯。
你需要判断红绿灯的颜色变化是否合理。

输入描述:

第一行依次输入三个整数 a,b,c(−1≤a,b,c≤1)a,b,c ,代表红绿灯的变化情况。
输出描述:

如果红绿灯的颜色变化合理则输出 YES ,否则输出 NO 。

解题思路: 按题目进行模拟即可, 记得看样例, 三个数, 1. 中间是0, 左右是+1/-1,

2. 中间是+1/-1.左右是0。 

#include <bits/stdc++.h>
using namespace std;
int main(){
    int a, b, c;
    cin >> a >> b >> c;
    bool f = false;
    if (b == 0) {
        if (abs(a) == 1 && abs(c) == 1 && a + c == 0) f = true;
    } else {
        if (a == 0 && c == 0 && abs(b) == 1)  f = true;
    }
    cout << (f ? "YES" : "NO");
    return 0;
}

 B. 签到题

题目链接:B-签到题_牛客小白月赛115

题目描述

众所周知,签到题是一场比赛里难度最低的题目。 假定比赛中难度最低的题目的难度为 x, 那么所有难度为 x 的题目都称之为"签到题"。
小 E 的题库里有 n 道题,第 i 道题的难度为 ai​。小 E 是良心出题人,他将在题库的 n 道题里选出 m 道题组成比赛题单,并使得题单里"签到题"的数目尽量多。
请输出"签到题"数量的最大值。

解题思路:从n道题中选出m个, 让选出来的m个数中最小值对应的数量最大

1. 先统计各个数出现的频次 2. n=5;m=3 ; 1 2 2 2 3, 答案就是[2,2,2], sum>=m表示从当前难度/大于当前难度的题中选m道, 后续不够m的话,就没有必要选了。

2. min(p.second,m)如果当前难度值的出现次数 p.second 大于 m,最多只能选 m 道(因为总共选 m 道)

3. 否则,可以选择所有 p.second 道,然后从更大的难度值中继续选择。ans 取当前最大值

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, m;
    cin >> n >> m;
    map<int,int> mp; int num;
    for(int i=0;i<n;i++){
        cin>>num;
        mp[num]++;
    }
    int ans=0,sum=n;
    for(auto& p:mp){
        if(sum>=m) ans=max(ans,min(p.second,m)); 
        sum-=p.second;
    }
    cout<<ans<<endl;
    return 0;
}

我赛时用的是前缀和数组, 代码如下:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    int maxNum = 0;
    for(int i = 0; i < n; i++){
        cin >> a[i];
        maxNum = max(maxNum, a[i]); 1. 找出最大的数字
    }
    vector<int> cnt(maxNum+1);  
    for(int x : a) cnt[x]++;        2. 统计各个数字的出现次数
    vector<int> pre(maxNum+1);
    pre[0] = cnt[0];
    for(int d = 1; d <= maxNum; d++){
        pre[d] = pre[d-1] + cnt[d];    3.前缀字符出现次数和
    }
    int ans = 0;
    for(int d = 1; d <= maxNum; d++){
        if(cnt[d] == 0) continue;
        if(cnt[d] >= m){
            cout << m << endl;
            return 0;
        }
        int g = n - pre[d];          4.剩余字符的总出现次数
        if(g >= (m - cnt[d])){       
            ans = max(ans, cnt[d]);
        }
    }
    cout << ans << endl;
    return 0;
}

C.命运之弹 

题目链接:  登录—专业IT笔试面试备考平台_牛客网

题目描述
本题为问题的简单版本,两题的唯一区别在于一些变量的数据范围。

在一处险恶的战场上,小 K 正面临着枪林弹雨的局面。现在有 n 颗子弹依次向他袭击。
冥冥之中定有天意,世上存在着一些虚无缥缈的运气,小 K 也有着一定的幸运值 v。第 i 颗袭来的子弹的危险值为 ai​ ,如果小 K 的幸运值不低于子弹的危险值,那么这颗子弹将被躲避。否则,小 K 将会被击杀。
为了保护小 K 的安全,小 E 偷偷给了小 K 两种魔法:
转瞬即逝」的机会:每一次使用,小 K 将自己突然隐匿,当前袭来的子弹的危险值变为 000。
「扭转乾坤」的机会:每一次使用,小 K 躲避当前来袭的子弹,并将幸运值永久地变为这颗子弹的危险值。
每一颗子弹到达前,小 K 都可以从这两种魔法中任选一种使用,也可以不使用。但是,「扭转乾坤」至多可以被使用 1 次。
现在小 E 想请你来想想,小 K 如果为了自己不被击杀,最少需要使用多少次「转瞬即逝」?

解题思路: 

转瞬即逝: ai -> 0
扭转乾坤: v -> ai

统计用了多少次转瞬即逝

考虑扭转乾坤, v变成ai, 还需要进行多少次转瞬即逝
对于操作扭转乾坤前面的数字, 我只需要统计大于初始幸运值v的个数即可

#include<bits/stdc++.h>
using namespace std;
int freq[101];
int main()
{
    int n,q,k,ans=INT_MAX;
    cin>>n; vector<int> a(n+1); vector<int> b(n+1); 记录前面位置大于初始幸运值的元素个数
    for (int i = 1; i <= n; i++) cin>>a[i];
    cin>>q;
    while (q--){
        int v;
        cin>>v;         初始幸运值
        int x=0;
        for(int i=1;i<=n;i++){
            b[i]=x;
            if(a[i]>v) x++;
        }
        for(int i=n;i>0;i--){
            freq[a[i]]++;       统计后面字符的出现次数 
            int backNum=0;
            for(int j=a[i]+1;j<=100;j++){      v变成ai后还有多少个比v大的数
                backNum+=freq[j];
            }
            ans=min(ans,backNum+b[i]);
        }
        cout<<ans<<endl;
    }
}

D.操作字符串 

题目链接:登录—专业IT笔试面试备考平台_牛客网

题目描述

小 E 某天在研究字符串,他给出了如下定义:
当字符串 b 由正整数个字符串 a 首尾相接拼接而成时,我们称字符串 b 被字符串 a 整除。
当存在字符串 c 能够同时被字符串 a 和字符串 b 整除时,我们称长度最小的字符串 c 为字符串 a 和字符串 b 的最小公倍字符串。
现在小 E 拿到了 n 个字符串,小 E 可以进行如下操作:选定一个字符串的任意一个字符,接着把它修改为任意的字符。
小 E 想要知道最少需要进行多少次操作,使得这 n 个字符串中任意两个字符串间都存在最小公倍字符串。

解题思路:

例子如下: 

s1="abac" ; s2="bc", s1 修改成 s1="acac", s2="ac", 次数为2

1. 题中说c能被a和b整除的话, c就是a和b的公倍字符串, 其实应该描述的是c是a和b的类似于公约数的东西, c能首尾相连构成a, 也能首尾相连构成b, 也就是枚举a和b的所有公约数长度中符合首尾相连性质的最大值。

2. 因此, 只有当所有字符串长度有共同周期x时,才存在公倍字符串。这个x必须是所有字符串长度的公约数

3.对于每个可能的周期x,我们需要让所有字符串在每个位置j上的字符相同

4. 在所有可能的x中,我们选择需要修改次数最少的那个

手推一下, 上面提到的例子: 

s1_Len=4, s2_Len=2, 最大公约数为2, 所以因子为1, 2

当x=1时, 

abac

bc

全修改成b/a都可以, 如果要全部修改成b, 需要修改4次

当x=2时

abac

bc

分割成3个x=2长度的字符串, 第0个位置出现次数最大时a, 所以全部修改成a, 需要1次

第1个位置出现次数最大的是c, 所以全部修改成c,需要1次,因此总共需要2次

min(2,4)=2, 所以答案就是2

#include <bits/stdc++.h>
using namespace std;
int gcd(int x,int y){
    return y==0?x:gcd(y,x%y);
}
int main(){
    int n; cin>>n;
    vector<string> A(n); vector<int> A_len(n); int g=0;
    for(int i=0;i<n;i++) {cin>>A[i]; A_len[i]=A[i].size(); g=gcd(g,A_len[i]);}
    vector<int> v;
    for(int i=1;i*i<=g;i++){
        if(g%i==0){
            v.push_back(i);
            if(i*i!=g) v.push_back(g/i);
        }
    }
    int ans = INT_MAX;
    for(int x: v){
        vector<array<int,26>> fq(x);
        vector<int> B(x, 0); 
        for(int i =0;i<n;i++){
            int L=A_len[i];
            int times =L/x;
            for(int j=0;j<x;j++){
                for(int t =0,idx=j;t<times;t++,idx += x){
                    int c = A[i][idx]-'a';
                    fq[j][c]++;
                }
                B[j]+=times;
            }
        }
        int c = 0;
        for(int i = 0; i < x; i++){
            int mx = 0;
            for(int j = 0; j<26; j++){
                mx = max(mx,fq[i][j]);
            }
            c+=(B[i] - mx);
        }
        ans=min(ans, c);
    }
    cout<<ans<<endl;
    return 0;
}

 E: 魔法卷轴

题目描述

魔法世界总有各种意外,某天,小 E 被卷入了魔法师们的斗争中。为了打断敌方魔法师的吟唱,他将使用他的魔法卷轴。
敌方魔法师共 n 人,第 i 个人吟唱的关键时间节点为,只有正好在吟唱关键时间节点触发爆炸效果才能打断吟唱。
时间节点从 1 开始,每个时间节点,小 E  最多 能使用一个卷轴,每个卷轴只能对一名敌方魔法师产生效果。卷轴分为两种,使用效果如下:
使用冰卷轴:若敌方魔法师无状态,则施加冻结状态,吟唱关键节点延后一秒。若敌方魔法师处于冻结状态,则无事发生。若敌方魔法师处于燃烧状态,则立刻触发爆炸。
使用火卷轴:若敌方魔法师无状态,则施加燃烧状态,吟唱关键节点提前一秒。若敌方魔法师处于燃烧状态,则无事发生。若敌方魔法师处于冻结状态,则立刻触发爆炸。
当敌方魔法师第一次受到冰/火卷轴的效果后,将产生对应魔法抗性,此后冰/火卷轴对其无效。
小 E 能否以合理的安排打断所有敌人的吟唱?

解题思路: 

首先,如果两名魔法师最终的关键时间一致,则无解。

由于对魔法师第一次使用卷轴时,关键时间会发生变化。我们可以先判断能否使得关键时间各不相同。同时,尽量使得关键时间节点延后以提供更多的时间进行操作。

可以按时间点从大到小枚举,并贪心地将关键点优先设置为 ai+1 ,其次才是 ai-1。

将关键时间节点贪心分配后,剩下的限制只有操作次数,我们不需要知道每个时间节点进行什么操作,只要到达关键节点时的空余操作数足够即可。

从小到大地枚举时间点需要考虑在当前剩余时间足够时将关键时间设置为 ai-1,否则后面容易使得关键时间点冲突,需要提前考虑空余操作数足够的影响。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        int n; cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) cin >> a[i];
        sort(a.begin(), a.end());
        map<int, int> mp;
        int f = 1;
        for(int i = n-1; i >= 0; i--) {
            int x = a[i]-1;
            int y = a[i]+1;
            if(mp.find(y)==mp.end()){
                mp[y] = 1;
            }
            else if(mp.find(x)==mp.end()){
                mp[x] = 1;
            }
            else {
                f = 0;
                break;
            }
        }
        int sum = 0, now = 0;
        for(auto& x: mp) {
            sum += x.first - now;
            now = x.first;
            sum -= 2;
            if(sum < 0) {
                f = 0;
                break;
            }
        }
        cout<< (f ? "YES" : "NO") << endl;
    }
}

F. 游戏高手

题目描述

小 E 和小 P 在玩一个游戏。这个游戏有 n 名角色,角色依次编号为 1−n。每名角色只能被一名玩家选用,由小 E 先手,小 E 和小 P 轮流选择一名未被选用的角色加入己方阵营,直到所有的角色都被选用。
小 E 和小 P 都是游戏高手,他们熟知游戏的角色间共有 m 条克制关系。特别的,这个游戏的策划保证了一名角色最多被一名角色克制,一名角色最多可以克制一名角色。不会有角色被自身克制。
当玩家 A 选择的某名角色能克制玩家 B 选择的某名角色时,玩家 A 的游戏舒适度 +1 ,当玩家 A 选择的某名角色被玩家 B 选择的某名角色克制时,玩家 A 的游戏舒适度 −1 。
小 E 和小 P 都希望最大化自己的游戏舒适度,为此,他们将选择最有利于自己的角色选择方案,请输出小 E 能达到的最大游戏舒适度。

 解题思路:比赛的时候没有写出来.... (其实根本没时间想)

G. 命运之弹(Hard Version) 

题目描述

小 E 和小 P 在玩一个游戏。这个游戏有 n 名角色,角色依次编号为 1−n。每名角色只能被一名玩家选用,由小 E 先手,小 E 和小 P 轮流选择一名未被选用的角色加入己方阵营,直到所有的角色都被选用。
小 E 和小 P 都是游戏高手,他们熟知游戏的角色间共有 m 条克制关系。特别的,这个游戏的策划保证了一名角色最多被一名角色克制,一名角色最多可以克制一名角色。不会有角色被自身克制。
当玩家 A选择的某名角色能克制玩家 B选择的某名角色时,玩家 A 的游戏舒适度 +1 ,当玩家 A 选择的某名角色被玩家 B 选择的某名角色克制时,玩家 A 的游戏舒适度 −1 。
小 E 和小 P 都希望最大化自己的游戏舒适度,为此,他们将选择最有利于自己的角色选择方案,请输出小 E 能达到的最大游戏舒适度。

解题思路:吃了三次罚时, 没想到赛后提交我最后一次修改的代码,居然过了,  依旧怯战....

在C题的基础上修改了数据范围,我们可以使用线段树来维护了一个数组的区间最小值,同时支持对任意区间进行增减操作。

1.其中, range_add(L, R, v):对区间 [L, R] 的所有元素增加 v

2.query_min():查询整个数组的最小值

3.Node中的x是当前区间的最小值

4.初始时,线段树用数组 s 初始化, 其中 s[i] 表示原数组 a 中第 i 个元素右侧比它小的元素个数(逆序数)

5.在update时,代码会逐步处理比当前查询值 v 大的元素,将这些元素的位置 i 对应的区间 [i+1, n] 加 1

6. 线段树维护的是动态调整后的逆序数数组的最小值

#include <bits/stdc++.h>
using namespace std;
// 区间增减+最小值查询
class SegmentTree {
    struct Node {
        int x;
        int y;
    };
    int n;
    vector<Node> s;
    void apply(int p, int v) {
        s[p].x += v;
        s[p].y += v;
    }
    void push(int p) {
        if (s[p].y) {
            apply(p*2,s[p].y);
            apply(p*2+1,s[p].y);
            s[p].y = 0;
        }
    }
    void build(int p, int l, int r, const vector<int>& a) {
        s[p].y = 0;
        if (l == r) {
            s[p].x = a[l];
            return;
        }
        int m = (l+r)/2;
        build(p*2, l, m, a);
        build(p*2+1, m+1, r, a);
        s[p].x = min(s[p*2].x, s[p*2+1].x);
    }
    void update(int p, int l, int r, int L, int R, int v) {
        if (L>r || R<l) return;
        if (L<=l && r<=R) {
            apply(p, v);
            return;
        }
        push(p);
        int m=(l+r)/2;
        update(p*2,l,m,L,R,v);
        update(p*2+1,m+1,r,L,R,v);
        s[p].x = min(s[p*2].x,s[p*2+1].x);
    }
public:
    SegmentTree(int _n, const vector<int>& init) : n(_n) {
        s.resize(4*n+4);
        build(1, 1, n, init);
    }
    void range_add(int L, int R, int v) {
        if (L>R) return;
        update(1, 1, n, L, R, v);
    }
    int query_min() const {
        return s[1].x;
    }
};
int main() {
    int n;
    cin >> n;
    vector<int> a(n+1), tmp(n);
    for(int i=1;i<=n;i++){
        cin >> a[i];
        tmp[i-1] = a[i];
    }
    sort(tmp.begin(), tmp.end());
    tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
    int m = tmp.size();
    auto g = [&](int x){
        return int(lower_bound(tmp.begin(), tmp.end(), x) - tmp.begin()) + 1;
    };
    vector<int> bit(m+1,0), s(n+2,0);
    auto b = [&](int x,int v){
        for(; x<=m; x+=x&-x) bit[x]+=v;
    };
    auto c= [&](int x){
        int r=0;
        for(; x>0; x-=x&-x) r+=bit[x];
        return r;
    };
    for(int i=n;i>=1;i--){
        int id = g(a[i]);
        s[i] = c(m) - c(id);
        b(id,1);
    }
    SegmentTree st(n, s);
    unordered_map<int, vector<int>> pos;
    for(int i=1;i<=n;i++){
        pos[a[i]].push_back(i);
    }
    int q;
    cin >> q;
    vector<pair<int,int>> qs(q);
    for(int i=0;i<q;i++){
        cin >> qs[i].first;
        qs[i].second = i;
    }
    sort(qs.begin(), qs.end(), greater<>());
    vector<int> vals = tmp; 
    reverse(vals.begin(), vals.end());
    vector<int> ans(q);
    int t = 0;  
    int z = 0; 
    for(auto [v, idx] : qs){
        while(z < vals.size() && vals[z] > v){
            int x = vals[z];
            for(int i : pos[x]){
                st.range_add(i+1, n, 1);
                t++;
            }
            z++;
        }
        int best = st.query_min();
        ans[idx] = min(t, best);
    }
    for(int i=0;i<q;i++){
        cout << ans[i] << endl;
    }
    return 0;
}

这场比赛的难度应该比[某蓝某桥某杯-京津冀]难个50-60倍吧

最后, 感谢大家的点赞和关注,你们的支持是我创作的动力! 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值