牛客小白月赛101

A-tb的区间问题

思路

前缀和
我的解法蠢了,就是做一个前缀和和一个后缀和,枚举前i个和后k-i个,取一个max值就是答案。
但其实这题不用这么麻烦,不管删除第一个还是最后一个,最后都会留下一组连续的大小为n-k的子数组,所以我们只需要做一个前缀和,从第n-k开始,每次作为结尾向前取n-k个数,取一个max值,就是答案。

代码

#include <iostream>

using namespace std;

typedef long long ll;

const int N = 5e3 + 10;

int n, k;
ll s[N];

int main()
{
    cin >> n >> k;
    
    for (int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        s[i] = s[i - 1] + x;
    }
    
    ll res = 0;
    for (int i = n - k; i <= n; i ++)
        res = max(res, s[i] - s[i - n + k]);
    
    cout << res;
    
    return 0;
}

B-tb的字符串问题

思路

单调栈的板子题吧
让每个字符进栈,如果栈顶的两个字符组成为目标字符串就出栈两个字符,同时答案res++。

代码

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n;
    cin >> n;
    
    string s;
    cin >> s;
    
    vector<char> st;
    for (int i = 0; i < n; i ++)
    {
        st.push_back(s[i]);
        if (st.size() > 1) {
            if (st[st.size() - 2] == 'f' && st[st.size() - 1] == 'c' || st[st.size() - 2] == 't' && st[st.size() - 1] == 'b')
            {
                st.pop_back();
                st.pop_back();
            }
        }
    }
    
    cout << st.size();
    
    return 0;
}

C-tb的路径问题

思路

打表找规律

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
1 1 3 1 1 3 1 1 3 1 1 3 1 1 3 1 1 3 1 1
1 2 1 4 1 2 1 4 1 2 1 4 1 2 1 4 1 2 1 4
1 1 1 1 5 1 1 1 1 5 1 1 1 1 5 1 1 1 1 5
1 2 3 2 1 6 1 2 3 2 1 6 1 2 3 2 1 6 1 2
1 1 1 1 1 1 7 1 1 1 1 1 1 7 1 1 1 1 1 1
1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 8 1 2 1 4
1 1 3 1 1 3 1 1 9 1 1 3 1 1 3 1 1 9 1 1
1 2 1 2 5 2 1 2 1 10 1 2 1 2 5 2 1 2 1 10
1 1 1 1 1 1 1 1 1 1 11 1 1 1 1 1 1 1 1 1
1 2 3 4 1 6 1 4 3 2 1 12 1 2 3 4 1 6 1 4
1 1 1 1 1 1 1 1 1 1 1 1 13 1 1 1 1 1 1 1
1 2 1 2 1 2 7 2 1 2 1 2 1 14 1 2 1 2 1 2
1 1 3 1 5 3 1 1 3 5 1 3 1 1 15 1 1 3 1 5
1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 17 1 1 1
1 2 3 2 1 6 1 2 9 2 1 6 1 2 3 2 1 18 1 2
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 19 1
1 2 1 4 5 2 1 4 1 10 1 4 1 2 5 4 1 2 1 20

打了一张20*20的图表,你就会发现

  • n = 1时,直接到达
  • n = 2时,需要2步
  • n = 3时,需要4步
  • n > 3时,分两个情况,偶数和奇数。偶数的情况,可以发现每个大于2的偶数左边都有一个2,可以通过2不消耗能量一步到达偶数的左边再消耗两步到达n;奇数的情况,同样通过2来一步到达奇数的左上,再消耗4步到达。

代码

#include <iostream>

using namespace std;

int main()
{
    int n;
    cin >> n;
    
    if(n == 1) cout << 0;
    else if(n == 2) cout << 2;
    else if(n == 3) cout << 4;
    else {
        if(n % 2 == 0) cout << 4;
        else cout << 6;
    }
    
    return 0;
}

D-tb的平方问题

思路

前缀和+差分
先进行前缀和,因为题目数据范围不大,直接暴力枚举区间,如果区间和为完全平方数就把该区间标记+1作差分,最后再前缀和一遍。

代码

#include <iostream>
#include <cmath>
#include <vector>
#include <map>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n, q;
ll a[N], s[N];
ll t[N];

bool is_(ll x)
{
    if (x == 0) return false;
    ll y = sqrt(x);
    if (y * y == x) return true;
    return false;
}

int main()
{  
    cin >> n >> q;
    for(int i = 1 ; i <= n ; i ++)
    {
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
    }

    for(int i = 1 ; i <= n ; i ++)
    {
        for(int j = 0 ; j < i ; j ++)
        {
            ll num = s[i] - s[j];
            if(is_(num)) t[j + 1] ++ , t[i + 1] --;
        }
    }
    
    for(int i = 1 ; i <= n ; i ++) t[i] += t[i - 1];
    
    while (q --)
    {
        int x; 
        cin >> x;
        
        cout << t[x] <<'\n';
    }

    
    return 0;
}

E-tb的数数问题

思路

官方给了一个线性筛筛因子数的知识点(又学到了)。
根据题意,凡是好数,它的所有因子都要在集合里,将集合里的数去重然后标记一下,再将不在集合里的数枚举,将它的所有倍数都筛掉,因为它不属于好数,它的倍数也不属于好数了,这样的话,我们只需枚举1~1e6,同时满足是集合里的数和没被筛掉的数两个性质就是好数。

代码

#include <iostream>
#include <set>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int n;
bool a[N], b[N];

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        a[x] = 1;
    }
    
    for (int i = 1; i < N; i ++)
        if (!a[i]) 
            for (int j = i; j < N; j += i) b[j] = 1;
               
    int res = 0;
    for (int i = 1; i < N; i ++)
        if (a[i] && !b[i]) res ++;
    
    cout << res;
    
    return 0;
}

F-tb的排列问题

思路

不会,以下是官方题解。
\(A\) 中未出现的数的集合为 \(P\)

枚举第 \(i\) 次操作,此时,对于 \(B\) 排列,我们有两种情况:

  1. \(b_i\) 不属于 \(P\): 此时 \(b_i\) 所对应的数如果在 \(A\) 中的初始位置就大于了 \(i + s\),则一定不存在操作使之复位,否则,其初始位置如果在 \(1\)\(i - 1\),则一定通过置换操作移动到了 \(i\)\(i + s\),则此次置换操作一定可以将其复位,能复位贡献为1,否则贡献为0。

  2. \(b_i\) 属于 \(P\): 此时 \(b_i\) 可以填入 \(1\)\(i + s\) 中所有为 \(-1\) 的位置,因为通过之前的操作,\(1\)\(i - 1\) 的位置均已复位,则之前未填的为 \(-1\) 的位置通过置换操作至多移到了 \(i - 1 + s\) 的位置,于是一定可以填充,此时贡献为 \(sum(i + s) - {cnts}\)\({cnts}\) 为已经填充的 \(-1\) 个数,\(sum(i + s)\)\(A\) 数组中下标为 \(1\)\(i + s\)\(-1\) 的个数;

最终答案为 \(1\)\(2\) 情况的所有贡献求积即可。

代码

#include <iostream>
#include <map>

using namespace std;

typedef long long ll;

const int mod = 998244353;
const int N = 2e5 + 10;

ll a[N], b[N], pre[N];

void solve()
{
    int n, w;
    cin >> n >> w;
    
    map<int, int> mp;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        
        mp[a[i]] = i;
        pre[i] = pre[i - 1] + (a[i] == -1);
    }
    for (int i = 1; i <= n; i ++) cin >> b[i];
    
    ll res = 1, cnt = 0;
    for (int i = 1; i <= n; i ++)
    {
        if (mp.count(b[i])) {
            if (mp[b[i]] > i + w) res = 0;
        }
        else {
            res = res * (pre[min(i + w, n)] - cnt) % mod;
            cnt ++;
        }
    }
    
    cout << res << '\n';
}

int main()
{
    int t;
    cin >> t;
    
    while (t --) solve();
    
    return 0;
}
posted @ 2024-09-21 00:57  Natural_TLP  阅读(17)  评论(0)    收藏  举报