牛客小白月赛65个人题解A-E

1. 牛客小白月赛65

A. 牛牛去购物

题意:给定n元,购买价格为a元的篮球和价格为b的篮球,数量不定,要使得花掉的钱最多,也就是剩余的钱数最少,求这个值 (1 <= n, a, b <= 1000)

思路:因为数据范围很小, 故直接双重循环暴力枚举购买篮球和足球的数量即可,注意一些取值范围,然后取min可得剩余的钱数最小值

#include 
using namespace std;

#define int long long
#define endl '\n'

void solve()
{
    int n, a, b;
    cin >> n >> a >> b;
    
    int res = 1e8;
    
    for (int i = 0; i <= n / a; i ++)
    {
        for (int j = 0; i * a + j * b <= n; j ++)
        {
            res = min(res, n - i * a - j * b);
        }
    }
    cout << res << endl;
}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    int T;
//     cin >> T;
    T = 1;
    while (T --) solve();
    return 0;
}

B.牛牛写情书

题意:给定s1字符串(仅包含小写字母a-z),而后对其某些位置插入s2 = “0123456789±*|,.~!@#$%^&()[]{}'”;:?<>/" 的子串,然后给定s3字符串,询问s1原串中是否有子串等于s3, (字符串长度 <= 5e3)

思路:由于给出的s1是已经被s2插入过的字符串,可以对照着s2字符串,还原出真正的s1字符串,然后普通的字符串查找子串即可, 值得注意的是:对于’\’ 和 ’ " ’ 这两个字符需要在其前面 加一个 ‘\’ 进行转义

#include 
using namespace std;

#define int long long
#define endl '\n'


void solve()
{
    int n, m;
    cin >> n >> m;
    
    string s1, s2;
    cin >> s1 >> s2;
    
    string s3 = "0123456789+-*|,.~!@#$%^&()[]{}'\";:?<>\\/";
    map<char, int> mp;
    for (int i = 0; i < s3.size(); i ++) mp[s3[i]] ++;
    
    string s4;
    for (int i = 0; i < s1.size(); i ++)
    {
        if (mp.count(s1[i])) continue;
        s4 += s1[i];
    }
    
    int n2 = s2.size();
//     cout << s4 << endl;
    for (int i = 0; i < s4.size(); i ++)
    {
        string t = s4.substr(i, n2);
        if (t == s2)
        {
            cout << "YES" << endl;
            return;
        }
    }

    cout << "NO" << endl;
}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    int T;
    //cin >> T;
    T = 1;
    while (T --) solve();
    return 0;
}

C.牛牛排队伍

题意:n个人排队, 1号->2号->3号…, 以此类推n-1号排在n号的前面, 有两个操作,操作1:将x号叫走(保证x号此时一定在队伍中); 操作2:求排在x号前面的是谁

思路:对于这种查询前面是某个人的,很容易想到链表,而且是必须是双向链表,因为我们要做的操作是:将x移走,那么就要求x的前一个人是谁,x的后一个人是谁,所以需要pre数组记录x的前一个人是pre[x],同理suff[x]记录x的后一个是suff[x],然后更新的过程画个图直觉体验更好, ps:(这道题卡vector,vector只能过95%的数据)

#include 
using namespace std;

#define int long long
#define endl '\n'
const int N = 1e6 + 10;
int pre[N], suff[N];

void solve()
{
    int n, k;
    cin >> n >> k;
    
    for (int i = 1; i <= n; i ++) pre[i] = i - 1;
    for (int i = 0; i <= n; i ++) suff[i] = i + 1;
    
    while (k --)
    {
        int id, x;
        cin >> id >> x;
        
        if (id == 1)
        {
            int t_pre = pre[x];
            int t_suff = suff[x];
            
            pre[t_suff] = pre[x];
            suff[t_pre] = suff[x];
        }
        else
        {
            cout << pre[x] << endl;
        }
    }
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    int T;
//     cin >> T;
    T = 1;
    while (T --) solve();
    return 0;
}

D.牛牛取石子

题意:有两堆石子,第一堆有a个,第二堆有b个,牛牛先手,牛妹后手,对于取石子有两种方案:(1) 第一堆取1个,第二堆取2个; (2) 第一堆取2个,第二堆取1个;对于选择的方案,必须保证当前石子 >= 取的石子个数才能取,谁先无法取石子,谁就输了,输入两堆石子的个数,判断谁获胜

思路:这个涉及到对称博弈

  1. 对于后手来说,只要选择与先手对称相反的操作,即先手选择方案1,那么后手就选方案2,这样就可以保证对于两个石堆来说都是 -3,且先手没有办法反制,因此对于两堆石子中的最小值,只要它是 % 3 == 0,那么一定是后手必胜,因为最小值能更快达到0这个必败态
  2. 那么相反,对于如果 两堆石子中的最小值,它不是 % 3 == 0,比如说是 % 3 == 1, 那么先手就取那个有最小值的石堆石子1个,最大值石堆石子2个,这样就可以保证 有最小石子的石堆 % 3 == 0, 这样又变成了情况1了,因此此时是先手必胜
  3. 考虑特殊情况,如果这两个石堆的石子数量一样,那么有最小值的余数有3钟情况:0, 1, 2; 分类讨论可得,对于余数为0的情况,后生必胜(1); 对于余数为1的情况,后手必胜(举个例子:(4,4)), 对于余数为2的情况(5, 5), 先手必胜
#include 
using namespace std;

#define int long long
#define endl '\n'

void solve()
{
    int a, b;
    cin >> a >> b;
    
    int r = min(a, b) % 3;
    
    if (r)
    {
        if (r == 1 and a == b) cout << "niumei" << endl;
        else cout << "niuniu" << endl;
    }
    else cout << "niumei" << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    int T;
    cin >> T;
//     T = 1;
    while (T --) solve();
    return 0;
}

E. 牛牛的构造

题意:给定数组a的长度n,数组a中的数是1~n的排列,求构造一个数组a,使得这个数组恰好存在k个二元组,满足 1 <= i < j <= n && a[i]-a[j] = 2^x (x属于非负整数) 如果不满足一个数组条件输出-1,反之输出这个数组

思路:考虑到要构成这种二元组,那么一定是逆序的,那么即有构造一个前部分顺序,后部分逆序的数组,这样就可以提供这种二元组,同时要考虑每个数字对于二元组个数的贡献

  1. 发现对于每个数字i, 满足 i + 2^x <= n,则有 x = log2(n - i), 再 + 1即为每个数的最大贡献,也就是 x + 1,例如对于数字1,它的贡献是 log2(5-1) + 1 = 3,因为向下取整的原因 + 1,同理对于数字2,log2(5-2) + 1 = 2
  2. 得到每个数字i的贡献后,将k总值减去对应的数字的贡献值,并将其放到最后
  3. 例如 5 5, 可得 3 4 5 2 1, 因为1贡献了3个二元组,2贡献了2个二元组
#include 
using namespace std;

#define int long long
#define endl '\n'

const int N = 1e6 + 10;
int flag1[N], flag2[N];
int lg[N];

void solve()
{
    int n, k;
    cin >> n >> k;
    
    for (int i = 2; i <= n; i ++) lg[i] = lg[i >> 1] + 1;
    
    for (int i = 1; i <= n; i ++) flag1[i] = 1;
    
    for (int i = 1; i < n; i ++)
    {
        int cha = n - i;
        if (lg[cha] + 1 <= k)
        {
            flag1[i] = 0;
            flag2[i] = 1;
            k -= lg[cha] + 1;
        }
        
        if (k == 0)
            break;
    }
    
    if (k > 0)
    {
        cout << -1 << endl;
        return;
    }
    
    for (int i = 1; i <= n; i ++)
        if (flag1[i]) cout << i << " ";
    
    for (int i = n; i >= 1; i --)
        if (flag2[i]) cout << i << " ";
}


signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
    int T;
//     cin >> T;
    T = 1;
    while (T --) solve();
    return 0;
}

你可能感兴趣的:(算法,c++,c语言,算法,数据结构)