牛客练习赛141总结

牛客练习赛141_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ

A-小柒与啦啦啦的博弈_牛客练习赛141

题意:有 \(n\) 个物品,价值为 \(a_i\),两个人轮流取,求各自的拿到的物品的最大价值和。

题解:排序后模拟取。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e5 + 5;
int n, a[N];

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    sort(a + 1, a + n + 1);
    ll sum1 = 0, sum2 = 0;
    for(int i = n; i >= 1; --i)
    {
        if((i & 1) == (n & 1)) sum1 += a[i];
        else sum2 += a[i];
    }
    printf("%lld %lld\n", sum1, sum2);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

B-小柒的交替数组_牛客练习赛141

题意:有一个长度为 \(n\) 的序列,每次操作可以使任意位置 \(+1\),问最少的操作次数,使得能找到一个长度为 \(m\) 的奇偶交替的子数组。

题解:前缀和求出一个区间在奇数位上的奇数和偶数,枚举长度为 \(m\) 的子数组求最小值。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e5 + 5;
int n, m, a[N], c[N];

void solve()
{
    n = read(), m = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), c[i] = c[i - 1] + ((i & 1) == (a[i] & 1));
    int ans = 0x3f3f3f3f;
    for(int i = 1; i <= n - m + 1; ++i)
    {
        int t = c[i + m - 1] - c[i - 1];
        ans = min(ans, min(m - t, t));
    }
    printf("%d\n", ans);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

C-小柒的幸运数_牛客练习赛141

题意:给定一个长度为 \(n\) 的字符串 \(s\) 和一个幸运数字 \(x\),定义一个字符串的价值为逆序对个数,对于 \(s\) 的所有子串 \(t\),求 \(t\) 的价值与幸运数字 \(x\) 的差的绝对值的最小值。

题解:设子串区间为 \([l,r]\),容易想到随着 \(l\) 增加,\(r\) 单调不降,枚举 \(l\),用单指针维护 \(r\),当左端点向右移动,减少的逆序对个数为所有大于 \(s_l\) 的字符,若当前逆序对个数小于 \(x\),则不断向右移动右端点,增加的逆序对个数为所有大于 \(s_{r+1}\) 的字符,途中得到的所有答案取 \(min\) 即为答案。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 25;
char s[N];
ll x;

ll cnt[26];

void solve()
{
    scanf(" %s", s + 1);
    int n = strlen(s + 1);
    x = read();
    int l = 1, r = 0;
    ll sum = 0, ans = x;
    while(l <= n)
    {
        while(sum <= x && r < n)
        {
            ++r;
            cnt[s[r] - 'a']++;
            for(int i = s[r] - 'a' + 1; i < 26; ++i) sum += cnt[i];
            ans = min(ans, abs(x - sum));
        }
        if(l <= r)
        {
            --cnt[s[l] - 'a'];
            for(int i = 0; i < s[l] - 'a'; ++i) sum -= cnt[i];
            ++l;
            ans = min(ans, abs(x - sum));
        }
    }
    printf("%lld\n", ans);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

D-小柒的特殊双节点_牛客练习赛141

题意:给定一棵树,对于每个节点 \(i\),求最大值 \(a_i\oplus a_j\),其中 \(j\)\(i\) 没有祖先关系(但可以有 \(j=i\))。

题解:采用正序DFS+反序DFS,用一个 \(trie\) 树处理最大值,当遍历到节点 \(i\) 时,求最大值 \(a_i\oplus a_j\),当遍历完节点 \(i\) 的所有子树后,向 \(trie\) 树中加入 \(a_i\)
这样做的话,当遍历到节点 \(i\) 时,\(trie\) 树上有所有 \(dfs\) 序小于 \(i\) 且与 \(i\) 没有祖先关系的节点的值,再反序DFS即为答案。

一点联想:考虑 \(tarjan\) 算法求有向图的强联通分量时,将边分为了树边,返祖边,横叉边三种,处理横叉边的方法是:用一个栈记录根节点到当前节点路径的所有节点,那些已经遍历过但不在栈中的点,就是横叉边指向的点,也即没有祖先关系的点。

不会正反DFS,也可以用可持久化 \(trie\) 树维护子树信息,全局 \(trie\) 树维护整体-根节点到当前节点的信息,在做差的 \(trie\) 树上处理询问,复杂度为 \(O(n\log n)\)

可持久化trie做法
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 5e5 + 5;
int n, a[N];
vector<int> to[N];

struct Trie1
{
    int ch[N * 25][2], Size[N * 25], root, tot;
    Trie1()
    {
        memset(ch, 0, sizeof(ch));
        memset(Size, 0, sizeof(Size));
        root = 1, tot = 1;
    }
    void insert(int x, int val)
    {
        int now = root;
        for(int i = 29; i >= 0; --i)
        {
            int d = (x >> i) & 1;
            if(!ch[now][d]) ch[now][d] = ++tot;
            now = ch[now][d];
            Size[now] += val;
        }
    }
    int get(int x)
    {
        int ans = 0, now = root;
        for(int i = 29; i >= 0; --i)
        {
            int d = (x >> i) & 1;
            if(Size[ch[now][d ^ 1]] > 0) ans += (1 << i), now = ch[now][d ^ 1];
            else now = ch[now][d];
        }
        return ans;
    }
}t1;

int ch[N * 200][2], Size[N * 200], tot, root[N];

void insert(int &k, int p, int x)
{
    k = ++tot;
    int now = tot;
    for(int i = 29; i >= 0; --i)
    {
        ch[now][0] = ch[p][0], ch[now][1] = ch[p][1];
        int d = (x >> i) & 1;
        ch[now][d] = ++tot;
        Size[tot] = Size[ch[p][d]] + 1;
        now = ch[now][d], p = ch[p][d];
    }
}

int dfn[N], low[N], rk[N], num;
int ans[N];
void dfs1(int k, int fa)
{
    dfn[k] = low[k] = ++num, rk[num] = k;
    for(auto v : to[k])
    {
        if(v == fa) continue;
        dfs1(v, k);
    }
    low[k] = num;
}

int query(int l, int r, int x)
{
    // printf("x = %d\n", x);
    int ans = 0, p = t1.root ;
    // printf("l = %d, r = %d\n", l, r);
    for(int i = 29; i >= 0; --i)
    {
        int d = (x >> i) & 1;
        // printf("i = %d, p = %d, Size = %d %d %d, d = %d\n", i, p, t1.Size[t1.ch[p][d ^ 1]], Size[ch[r][d ^ 1]], Size[ch[l][d ^ 1]], d);
        assert(t1.Size[t1.ch[p][d ^ 1]] - Size[ch[r][d ^ 1]] + Size[ch[l][d ^ 1]] >= 0);
        if(t1.Size[t1.ch[p][d ^ 1]] - Size[ch[r][d ^ 1]] + Size[ch[l][d ^ 1]] > 0) 
        {
            ans += (1 << i);
            l = ch[l][d ^ 1], r = ch[r][d ^ 1], p = t1.ch[p][d ^ 1];
        }else l = ch[l][d], r = ch[r][d], p = t1.ch[p][d];
    }
    return ans;
}

void dfs2(int k, int fa)
{
    ans[k] = query(root[dfn[k] - 1], root[low[k]], a[k]);
    t1.insert(a[k], -1);
    for(auto v : to[k])
    {
        if(v == fa) continue;
        dfs2(v, k);
    }
    t1.insert(a[k], 1);
}

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), t1.insert(a[i], 1);
    for(int i = 1; i < n; ++i)
    {
        int u = read(), v = read();
        to[u].emplace_back(v);
        to[v].emplace_back(u);
    }
    dfs1(1, 0);
    for(int i = 1; i <= n; ++i)
    {
        insert(root[i], root[i - 1], a[rk[i]]);
    }
    dfs2(1, 0);
    for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

E-小柒的特殊五元组_牛客练习赛141

题意:给定长度为 \(n\) 的序列,求有多少个长度为 \(5\) 的子序列 \(b=\{b_1,b_2,b_3,b_4,b_5\}\) 满足大小关系为 \(b_2<b_1<b_3<b_5<b_4\)

题解:发现 \(b_2<b_1<b_3\)\(b_3<b_5<b_4\) 类似,以下只考虑求 \(b_2<b_1<b_3\)
枚举 \(a_i=b_3\),记前 \(i-1\) 个数中有 \(c\) 个小于 \(a_i\) 的数,从其中选择两个的方案数为 \(C_{c}^{2}\),其中大小关系为 \(b_1<b_2,b_1=b_2,b_1<b_2\),其中 \(b_1=b_2<b_3\) 容易算出,\(b_1<b_2<b_3\) 为三元上升子序列计数,容斥即可算出 \(b_2<b_1<b_3\) 的方案数。

考虑一种暴力做法:枚举 \(a_i=b_3\),在前 \(i-1\) 个数中,设 \(cnt_i\) 为值为 \(i\) 的数的个数,设 \(num_i\) 为较大值为 \(i\) 的逆序对数,枚举到 \(a_{i+1}=b_3\) 时,将 \(a_i\)\(cnt\)\(num\) 的计算出来,发现可以用分块维护 \(cnt\)\(num\) 数组。

分块做法
#include<bits/stdc++.h>
using namespace std;

#define int ll
#define ll long long
#define ull unsigned long long
#define int128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 3e5 + 5;
int n, a[N];
int Size, block;
int belong[N], L[N], R[N];

ll cnt[N], num[N]; // 元素个数,小于它的逆序数
ll c1[N], c2[N]; // 块中元素个数,块中逆序数总和
ll tag[N]; // 整体加标记

void pushdown(int b)
{
    if(tag[b])
    {
        for(int i = L[b]; i <= R[b]; ++i) num[i] += cnt[i] * tag[b];
        tag[b] = 0;
    }
}

void add(int x)
{
    int b = belong[x];
    for(int i = b + 1; i <= block; ++i) ++tag[i], c2[i] += c1[i];
    pushdown(b);
    for(int i = x + 1; i <= R[b]; ++i) num[i] += cnt[i], c2[b] += cnt[i];
    ++cnt[x], ++c1[b];
}

ll query(int x)
{
    int b = belong[x];
    ll ans = 0;
    for(int i = 1; i < b; ++i) ans += c2[i];
    pushdown(b);
    for(int i = L[b]; i < x; ++i) ans += num[i];
    return ans;
}

ll l[N], r[N];

void print(int128 ans)
{
    ll sta[100] = {0}, top = 0;
    do
    {
        sta[++top] = ans % 10;
        ans /= 10;
    }while(ans);
    while(top) printf("%lld", sta[top--]);
    printf("\n");
}

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    Size = sqrt(n);
    for(int i = 1; i <= n; i += Size) belong[i] = 1;
    for(int i = 2; i <= n; ++i) belong[i] += belong[i - 1];
    for(int i = 1; i <= n; ++i)
    {
        if(belong[i] != belong[i - 1]) L[belong[i]] = i, R[belong[i - 1]] = i - 1;
    }
    block = belong[n];
    R[belong[n]] = n;
    for(int i = 1; i <= n; ++i)
    {
        add(a[i]);
        if(i < n) l[i + 1] = query(a[i + 1]);
    }
    for(int i = 0; i <= n + 1; ++i) cnt[i] = num[i] = 0;
    for(int i = 0; i <= block + 1; ++i) c1[i] = c2[i] = 0, tag[i] = 0;
    for(int i = 1; i <= n; ++i) a[i] = n - a[i] + 1;
    for(int i = n; i >= 1; --i)
    {
        add(a[i]);
        if(i > 1) r[i - 1] = query(a[i - 1]);
    }
    int128 ans = 0, p1, p2;
    for(int i = 3; i + 2 <= n; ++i)
    {
        p1 = l[i], p2 = r[i];
        ans += p1 * p2;
    }
    print(ans);
}

signed main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}

F-三角符文(easy version)_牛客练习赛141
G-三角符文(hard version)_牛客练习赛141

题意:有一个边长为 \(n\) 的的三角矩阵,第 \(i\) 行有 \(i\) 个字符,字符只有 \(a,b,c\),最后一行的 \(n\) 个字符已给出,第 \(i\) 行第 \(j\) 个字符按照以下方式确定:
\(x_{i+1,j}=x_{i+1,j+1}\),则 \(x_{i,j}=x_{i+1,j}\)
\(x_{i+1,j}\neq x_{i+1,j+1}\),则 \(x_{i,j}\) 为和它们都不同的字符。
\(q\) 次询问,每次查询第 \(i\) 行第 \(j\) 个字符是什么。

题解:
令字符 \(a,b,c\) 分别为 \(0,1,2\),发现 \(x_{i,j}\) 满足 \(x_{i,j}\equiv -(x_{i+1,j}+x_{i+1,j+1})\bmod 3\)
这是单次递推,尝试寻找多次递推:

\[x_{i+d,j}\equiv(-1)^d\sum_{k=0}^{d}\binom{d}{k}x_{i,j+k}\bmod 3 \]

并没有简化计算,尝试寻找合适的 \(d\) 使得能够加速计算,比如绝大多数组合数在 \(\bmod3\) 意义下为 \(0\)
由卢卡斯定理可知,若 \(\binom{d}{k}\bmod3=0\),当且仅当在三进制下,并不是每一位都是 \(d_i\ge k_i\)

如果取 \(d=3^m\),则 \(\forall 0<k<d\),都有 \(\binom{d}{k}\bmod3=0\)

但是如果直接对询问三进制分解的话,若分解出 \(t=\log_3 n\) 个数,则需要 \(2^{t+1}-1\) 次递推,仍然无法接受。

考虑预处理一些行,预处理 \(L\) 行后,会分解出 \(\log_3 \frac{n}{L}\) 个数,需要 \(O((\frac{n}{L})^{\log_3 2})\) 次递推。取 \(L=729\) 可以通过。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define i128 __int128

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 5e5 + 5, M = 750;
int n, q;
short p[M][N];
char s[N];
int lg[N], Pow[N];

int get(int x, int y)
{
    if(n - x + 1 <= 729) return p[n - x + 1][y];
    int d = n - x;
    return (6 - get(x + Pow[lg[d]], y) - get(x + Pow[lg[d]], y + Pow[lg[d]])) % 3;
}

void solve()
{
    n = read(), q = read();
    scanf(" %s", s + 1);
    for(int i = 1; i <= n; ++i) p[1][i] = s[i] - 'a';
    for(int i = 2; i <= min(n, 729); ++i)
        for(int j = 1; j <= n - i + 1; ++j)
            p[i][j] = (3 - p[i - 1][j] - p[i - 1][j + 1] + 3) % 3;
    lg[0] = -1;
    for(int i = 1; i <= n; ++i) lg[i] = lg[i / 3] + 1;
    Pow[0] = 1;
    for(int i = 1; Pow[i - 1] < n; ++i) Pow[i] = Pow[i - 1] * 3;
    while(q--)
    {
        int x = read(), y = read();
        int d = n - x + 1;
        if(d <= 729) printf("%c", p[d][y] + 'a');
        else
        {
            int ans = get(x, y);
            printf("%c", ans + 'a');
        }
    }
}

int main()
{
    int T = 1;
    while(T--) solve();
    return 0;
}
posted @ 2025-06-23 15:35  梨愁浅浅  阅读(46)  评论(1)    收藏  举报