牛客小白月赛76 (A ~ G)

A.猜拳游戏

题意:玩猜拳游戏,对方会告诉我们他会出什么,但是他会比较狡猾,如果我们按照他说的去出(比如他说出石头,我们出布,但他想赢我们他就会出剪刀),我们想办法打败他。

思路:我们就根据他说的直接出相同的就行,他说出剪刀,他实际会出布(以为我们出石头),我们出剪刀即可。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    string s;
    cin >> s;
    cout << s;
    return 0;
}

B.Kevin喜欢一

题意:一个文本框初始有1个1,我们可以选中文本框的部分和全部字符复制粘贴到文本框末尾,问我们想让文本框中恰好有n个1,请求出最少的操作次数。

思路:直接用2^i去判断是否大于等于n,输出i。模拟放最多的顺序就是1,2,4,8,...,2^k,当2^i>n时不全部复制粘贴,选部分。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;

void solve()
{
    ll n;
    cin >> n;
    for(ll i = 0; i <= n; i ++ )
    {
        if((1 << i) >= n)
        {
            cout << i << '\n';
            break;
        }
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

C.A加B,A模B

题意:给出n,m。找出两个整数a和b,并使满足 a + b = n, a mod b = m, 0 <= a <= 1e9, 1<=b<=1e9.找不到输出-1。

思路:

n<=m显然不行,m的最大值可达是b-1,而n = a + b,显然n > m.a = kb + m,b > m, k >= 0,a + b = (k+1)b + m = n,(k+1)b > m,2 * m >= n是无解的。令k=0,b = n-m,a = m,.此时n > 2m,得出n-m > m,a % b = m.所以n > 2m时,a = m, b = n - m是可行解

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;

void solve()
{
    int n, m;
    cin >> n >> m;
    if(n > 2 * m) 
        cout << m << ' ' << n - m << '\n';
    else cout << -1 << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

D.MoonLight的运算问题

题意:我们拥有一个数字x,给我们长度为n的序列a,从第一个元素到最后一个元素顺序,对每一个元素我们可以进行x = x * a_ix = x + a_i,让我们求出x的最大值,并输出x模上998244353的值。

思路:我以为是动态规划f[i][0]前i个数当前第i个数我们选择加的最大值,f[i][1]选择乘,通过不了,后面思考了下,举了个反例,比如当前我们选择对第i个数操作,我们前面的状态是已经经过取模, a[i] > 1,此时我们选择乘更优,但是我们前面模的值可能是0,但我们取的模后的值,对于状态计算就出错了。对于这个样例

1
2
998244353 2

f[1][0] = 0, f[2][0] = 2;

f[1][1] = 0, f[2][1] = 0;

其实最大值是998244353*2 (%mod),但计算过程中取模导致max的错误状态转移,其实就是当前操作选择错误。

正确的思路,对于x*a_i<x+a_i,我们发现x>1a_i>1。初始x = 0,只能选择加,当x<=1时还是只能加,因为此时加更优,比如1 + 2 > 1 * 2。但当x>1后,ai<=1,我们选择加,ai>1,我们选择乘,因为x的实际值从现在开始是始终大于1的,只不过可能经过取模得到0或1。

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 998244353;
typedef  long long ll;
typedef pair<int,int> pii;



void solve()
{
    int n;
    cin >> n;
    ll res = 0;
    vector<ll> a;
    for(int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        if(x >= 1) a.push_back(x); //对于0我们选择加,相当于不影响
    }
    int f = 0; //判断x是否大于1,代码中res表示x
    for(auto i : a)
    {
        if(f == 0)
        {
            res += i;
            if(res > 1) f = 1;
            res %= mod;
        }
        else 
        {
            if(i == 1) res = (res + 1) % mod; //ai==1加
            else res = res * i % mod;         //ai>1乘
        }
    }
    cout << res << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

E.括号序列操作专家

题意:我们给出字符串只包含'('和')',我们可以对它进行操作交换任意两个相邻括号,判断进行操作后(有可能不操作),能否使括号序列合法,能输出最少次数,不能输出-1。一个括号序列是合法的,当且仅当此括号序列可以通过插入加号'+'和数字1得到一个正确的算术表达式。

思路:无法使之合法,左右括号数不相等。通过我用手不断把玩第三个样例点,发现如果前面有')'没匹配的话,当前一遇到'('先将之与最前面未匹配的')"进行匹配。如果'('未匹配的话,就等')'出现匹配最靠近'('的匹配。用两个指针hh,tt维护未匹配右括号的个数tt-hh+1,一旦出现左括号就将最远的右括号匹配,并将hh++,新出现无法匹配的右括号tt++。证明的话,对于未匹配右括号,我们出现左括号一定匹配最远未匹配的右括号最优,举例子"))((",如果按照我说的去匹配操作次数为3次,而匹配1和4,2和3的话要4次操作。对于最远未匹配右括号与当前左括号相匹配,会将中间未匹配的右括号向右移更靠近下一个左括号,而且这些未匹配的右括号中间没有匹配好的括号,不会增加多的移动次数。

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;

void solve()
{
    int n;
    cin >> n;
    string s;
    cin >> s;
    int cnt = 0;
    for(int i = 0; i < s.size(); i ++ ) 
        if(s[i] == '(') cnt ++ ;
    if(n % 2 != 0 || cnt * 2 != n)
    {
        cout << -1 << '\n';
        return ;
    }
    vector<int> q(n + 1);
    int hh = 0, tt = -1;
    cnt = 0; //')'个数
    ll res = 0;
    for(int i = 0; i < s.size(); i ++ )
    {
        if(cnt > 0 && s[i] == '(') 
        {
            cnt--;
            res += tt - hh + 1;
            hh++;
        }
        else if(s[i] == ')') 
        {
            if(cnt < 0) 
            {
                cnt++;
            }
            else 
            {
                cnt++;
                tt++;
            }
        }
        else if(s[i] == '(')
        {
            cnt--;
        }
    }
    cout << res << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

F.Kevin的矩阵

题意:给我们一段序列a和一个空矩阵(行数不限,列数为m),有三个操作,一是任选序列的某个位置,将此位置的数字修改为任意的数字。二是将矩阵的列数增加1。三是将矩阵的列数减小1(如果当前矩阵的列数大于1)。操作完后将序列从上到下,从左到右移除放进矩阵,我们想要矩阵有一列所有数字均为目标数字k,求出最少操作次数。

思路:虽然矩阵列数初始为m(1~1e9),序列长度最长为n(1~2e5),其实矩阵列数枚举也只需要到1~n,大于n后,每一列元素只有一个。可是这样所有枚举复杂度达到O(n^2)。

看到官方题解,提供的解法,当m=1,将矩阵的宽度调整到sqrt(n),此时花费的次数sqrt(n)-1,然后每一列的长度为sqrt(n),假设修改sqrt(n)个数最终次数总和2 * sqrt(n),因此我们发现答案不超过2 * sqrt(n)的规模,现在我们枚举的变化后的列的宽度的范围就变小了,不是1~n,设最初的矩阵宽度为m,而是[m - 2 * sqrt(n), m + 2 * sqrt(n)],枚举这个范围之外的宽度的话,光调整矩阵宽度的操作次数就已经超过2 * sqrt(n)。现在的时间复杂度O(n*sqrt(n))。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;

void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    int ans = 2e9;
    vector<int> a(n + 1);
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    //当m = 1,将矩阵的宽度调整到sqrt(n),此时花费的次数sqrt(n)-1,然后每一列的长度为sqrt(n),假设修改sqrt(n)个数
    //最终次数总和2 * sqrt(n),因此我们发现答案不超过2 * sqrt(n)的规模
    //现在我们枚举的变化后的列的宽度的范围就变小了,不是1~n,设最初的矩阵宽度为m,而是[m - 2 * sqrt(n), m + 2 * sqrt(n)]
    //枚举这个范围之外的宽度的话,光调整矩阵宽度的操作次数就已经超过2 * sqrt(n)
    int b = 2 * sqrt(n) + 5; 
    for(int c = max(1, m - b); c <= min(n, m + b); c ++ ) //枚举矩阵宽度
    {
        for(int i = 1; i <= c; i ++ ) //统计每一列的需要操作次数
        {
            int cnt = 0; //与目标值不同的个数
            for(int j = i; j <= n; j += c)
            {
                if(a[j] != k) cnt ++ ;
            }
            // if(ans > cnt + abs(m - c)) cout << c << ' ' << cnt + abs(m - c) << endl;
            ans = min(ans, cnt + abs(m - c));
        }
    }
    cout << ans << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

G.Kevin逛超市

题意:有n件物品,其中有一件物品会使报警,起初将这些物品n,mid = n / 2,先分成[1,mid],[mid+1,n],我让[1,mid]这堆进入报警器,如果报警,并且这堆物品中只有一个物品,那么就确定了这个物品结束检测,否则虽然报警可是有多个物品,我们仍要对这堆物品继续分并检测。不报警的话,就拿未检测的那一堆继续检测。接下来假设当前的物品的集合是[l, r],设mid = (l + r) / 2,那么分为[l, mid]和[mid + 1, r]继续进行上述操作,直到找到让报警器报警的物品。我们想知道将物品通过报警器的次数的期望。

思路:

样例解释给出1件物品,2件物品,3件物品的时候期望,我们发现期望为每件物品的查找次数/物品个数。我当时以为不断去递归去找n件物品的每个查找次数就行了,后来超时,找规律发现了特殊的地方,当时并没有明白是什么,设f[i]是i个物品通过报警器的次数总和,f[i] = f[i/2] + f[i - i/2] + i。

举例3个物品,第一个物品我们查询2次,第二个物品查询3次,第三个物品查询2次。对于2个物品

中,第一个物品查询1次,第二个物品2次。对于1个物品,第一个物品查询1次。将3个物品分成2个和1个物品,先检测后再选择一堆不就相当于单独检测2个物品或单独检测1个物品的次数总和,不过不要忽视我们在选之前要先检测一次,这样的话,这n个物品各自在原本的基础上检测次数加1,因此加上当前物品个数。

代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, mod = 998244353;
typedef long long ll;
typedef pair<int,int> pii;

unordered_map<ll, ll> mp;

ll dfs(int x)
{
    if(x == 1) return 1;
    if(mp[x]) return mp[x];
    int k = x / 2;
    int o = (dfs(k) + dfs(x - k) + x) % mod;
    return mp[x] = o;
}

ll qmi(ll a, ll b)
{
    ll res = 1;
    while(b)
    {
        if(b & 1) res = (res * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return res % mod;
}

void solve()
{
    int n;
    cin >> n;
    cout << dfs(n) * qmi(n, mod - 2) % mod << '\n';
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int _;
    _ = 1;
    mp[1] = 1, mp[2] = 3, mp[3] = 7;
    cin >> _;
    while(_--)
    {
        solve();
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
牛客 a卷2022年第四季度的华为题目中,要求考生设计一种高效的数据结构,能够支持以下几种操作: 1. 添加一个元素 2. 删除一个元素 3. 查找是否存在某个元素 4. 返回元素的总数 该数据结构要求满足空间复杂度较小、时间复杂度较低、能够快速地进行查找和修改等多种操作。 想要编写这样一种数据结构,我们可以参考许多已有的经典算法与数据结构,如二叉树、哈希表、红黑树等,通过综合利用它们的优点来实现这个问题的解决。 例如,我们可以通过哈希表来存储所有元素的值,并在每个哈希链表的元素中再使用红黑树来进行排序与查找。这样,我们既能够轻松地进行元素的添加和删除操作,也能够在查找较大数据范围和数量时保持较高的速度与效率。同时,由于使用了多个数据结构来协同完成这个问题,我们也能够在空间复杂度上适度地进行优化。 当然,在具体设计这个数据结构的过程中,我们还需要考虑一些实践中的细节问题,例如如何避免哈希冲突、如何处理数据丢失与被删除元素所占用的空间等问题,这都需要相应的算法与流程来进行处理。 总体来看,设计这种支持多种操作的高效数据结构,需要我们具备丰富的算法知识和编程实践能力,同时需要我们在具体处理问题时能够将多种算法和数据结构进行有效地结合。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值