2024牛客暑期多校训练营1

A. A Bit Common

Problem

给定两个整数 n n n m m m,问有多少个长度为 n n n 的序列 { A } i = 1 n ( A i < 2 m ) \{A\}_{i=1}^n (A_i< 2^m) {A}i=1n(Ai<2m) 满足存在一个非空子序列的按位与之和为 1 1 1。答案对整数 q q q 取模。

数据范围: 1 ≤ n , m ≤ 5000 , 1 ≤ q ≤ 1 0 9 1\le n,m\le 5000,1\le q\le 10^9 1n,m5000,1q109

Soultion

先考虑一个问题:有多少个不同的序列 { B } i = 1 n ( B i < 2 m ) \{B\}_{i=1}^n (B_i< 2^m) {B}i=1n(Bi<2m) 使得按位与之和为 0 0 0,即 B 1 & B 2 & … & B n = 0 B_1 \& B_2\& \dots\&B_n=0 B1&B2&&Bn=0

B i B_i Bi 转化为二进制的形式后,每一位上都至少存在一个 0 0 0,这样按位与后该位才能变为 0 0 0。对于长度为 n n n 的序列每一位的方案数为 2 n − 1 2^n-1 2n1(减去全为 1 1 1 的情况),共有 m m m 位,那么这样的序列个数共有 ( 2 n − 1 ) m (2^n-1)^m (2n1)m 个。用 f i , j f_{i,j} fi,j 表示长度为 i i i,位数为 j j j 时不同的序列个数,那么 f i , j = ( 2 i − 1 ) j f_{i,j}=(2^i-1)^j fi,j=(2i1)j

回到原来的问题,序列 { A } i = 1 n \{A\}_{i=1}^n {A}i=1n 中的数可以分为奇数和偶数,其中如果存在一个非空子序列按位与之和为 1 1 1,那么这个子序列中的数只能是奇数。同时子序列得到的结果是 000 … 001 000\dots001 000001,那么其他奇数一起按位与得到结果也为 1 1 1,因此问题的条件可以转化为对于奇数需要满足按位与之和为 1 1 1。只需要把最后一位去掉,就可以转换为前面的形式,个数就是 f i , m − 1 f_{i,m-1} fi,m1

最后的答案为 ∑ i = 1 n ( n i ) f i , m − 1 ( 2 m − 1 ) n − i \sum_{i=1}^n \binom{n}{i} f_{i,m-1}(2^{m-1})^{n-i} i=1n(in)fi,m1(2m1)ni,其中 ( n i ) \binom{n}{i} (in) 表示长度为 n n n 的序列中奇数个数为 i i i 的方案数, f i , m − 1 f_{i,m-1} fi,m1 表示的就是奇数的方案数, ( 2 m − 1 ) n − i (2^{m-1})^{n-i} (2m1)ni 表示偶数的方案数, m − 1 m-1 m1 是因为偶数最后一位有限制, n − i n-i ni 是偶数的数量。

时间复杂度: O ( n 2 ) O(n^2) O(n2),需要预处理组合数。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
int n, m;
ll C[N + 5][N + 5], M;
ll fpow(ll a, ll b)
{
    ll res = 1;
    a %= M;
    while (b)
    {
        if (b & 1)
        {
            res = res * a % M;
        }
        a = a * a % M;
        b >>= 1;
    }
    return res;
}
int main()
{
    cin >> n >> m >> M;
    for (int i = 0; i <= N; i++)
    {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++)
        {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % M;
        }
    }

    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans = (ans + C[n][i] * (fpow(fpow(2, i) - 1, m - 1)) % M * fpow(fpow(2, m - 1), n - i) % M) % M;
    }
    printf("%lld\n", ans);
    return 0;
}

B. A Bit More Common

Problem

给定两个整数 n n n m m m,问有多少个长度为 n n n 的序列 { A } i = 1 n ( A i < 2 m ) \{A\}_{i=1}^n (A_i< 2^m) {A}i=1n(Ai<2m) 满足至少存在两个不同非空子序列的按位与之和为 1 1 1。答案对整数 q q q 取模。

数据范围: 1 ≤ n , m ≤ 5000 , 1 ≤ q ≤ 1 0 9 1\le n,m\le 5000,1\le q\le 10^9 1n,m5000,1q109

Soultion

先考虑一个问题:有多少个不同的序列 { B } i = 1 n ( B i < 2 m ) \{B\}_{i=1}^n (B_i< 2^m) {B}i=1n(Bi<2m) 使得按位与之和为 0 0 0,即 B 1 & B 2 & … & B n = 0 B_1 \& B_2\& \dots\&B_n=0 B1&B2&&Bn=0,同时缺少任何一个数都不等于零。

B i B_i Bi 转化为二进制的形式后,每一位上都至少存在一个 0 0 0,这样按位与后的该位才能变为 0 0 0。又要求缺少任何一个数都不等于零,那么对于每个数来说必须要存在至少一个特殊位,即除了这个数该位为 0 0 0,其它数在这一位都为 1 1 1

先不考虑其他位的情况,只考虑这 j j j 个特殊位的方案数。这里提供两种求解方法,任选一种都可以。

方法一:对于有 j j j 个特殊位的序列,可以将这些特殊位看成不同的球,问题可以转换为将 j j j不同的球放入 n n n不同的箱子中且箱子非空的方案数,就是 S ( j , n ) A n n S(j,n) A_n^n S(j,n)Ann,其中 S ( j , n ) S(j,n) S(j,n) 为第二类斯特林数, A n n A_n^n Ann 为排列数。

方法二:用 d p dp dp 的方法求解,记 f i , j f_{i,j} fi,j 表示长度为 i i i 共有 j j j 个特殊位的方案数,状态转移方程如下:
f i , j = i ( f i , j − 1 + f i − 1 , j − 1 ) f_{i,j}=i(f_{i,j-1}+f_{i-1,j-1}) fi,j=i(fi,j1+fi1,j1)
解释:对于新的特殊位可以出现在原来的数中,此时有 i × f i , j − 1 i\times f_{i,j-1} i×fi,j1 种,也可以作为单独的一个数出现,这个数有 i i i 个位置可以插入,此时有 i × f i − 1 , j − 1 i\times f_{i-1,j-1} i×fi1,j1 种,将这两部分加起来即可。

s u m i sum_i sumi 表示长度为 i i i 的序列的个数,那么 s u m i = ∑ j = 1 m ( m j ) S ( j , i ) A i i ( 2 i − i − 1 ) m − j sum_i=\sum_{j=1}^{m} \binom{m}{j}S(j,i)A_i^i(2^i-i-1)^{m-j} sumi=j=1m(jm)S(j,i)Aii(2ii1)mj,其中 S ( j , i ) A i i S(j,i)A_i^i S(j,i)Aii 换成 f i , j f_{i,j} fi,j 也可以。

解释: ( m j ) \binom{m}{j} (jm) 表示从 m m m 位中选择 j j j 个特殊位的方案数, ( 2 i − i − 1 ) m − j (2^i-i-1)^{m-j} (2ii1)mj 表示其他位的方案数, ( 2 i − i − 1 ) (2^i-i-1) (2ii1) 是因为要减去全为一以及为特殊位的情况,共有 i + 1 i+1 i+1 种,剩下 m − j m-j mj 位。

回到原来的问题,其实该问题已经被解决了,题目要求的是至少存在两个不同的子序列,那么只要将上一个问题的答案减去只存在一个子序列的个数即可。而只存在一个子序列的个数可以通过之前考虑的问题得到。

最后的答案 ∑ i = 1 n ( n i ) ( ( 2 i − 1 ) m − 1 − s u m i ) ( 2 m − 1 ) n − i \sum_{i=1}^n \binom{n}{i}((2^i-1)^{m-1}-sum_i)(2^{m-1})^{n-i} i=1n(in)((2i1)m1sumi)(2m1)ni

时间复杂度: O ( m n ) O(mn) O(mn)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
int n, m;
ll C[N + 5][N + 5], f[N + 5][N + 5], S[N + 5][N + 5], A[N + 5], p[N + 5], sum[N + 5], M;
ll fpow(ll a, ll b)
{
    ll res = 1;
    a %= M;
    while (b)
    {
        if (b & 1)
        {
            res = res * a % M;
        }
        a = a * a % M;
        b >>= 1;
    }
    return res;
}
int main()
{
    cin >> n >> m >> M;
    for (int i = 1; i <= N; i++)
    {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++)
        {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % M;
        }
    }
    S[0][0] = 1;
    for (int i = 1; i <= N; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            S[i][j] = (S[i - 1][j - 1] + S[i - 1][j] * j) % M;
        }
    }
    A[0] = 1;
    for (int i = 1; i <= N; i++)
    {
        A[i] = A[i - 1] * i % M;
    }
    // f[0][0] = 1;
    // for (int i = 1; i <= N; i++)
    // {
    //     for (int j = i; j <= N; j++)
    //     {
    //         f[i][j] = (f[i][j - 1] * i % M + f[i - 1][j - 1] * i % M) % M;
    //     }
    // }
    p[0] = 1;
    for (int i = 1; i <= N; i++)
    {
        p[i] = p[i - 1] * 2 % M;
    }

    for (int i = 1; i <= n; i++)
    {
        ll a = (p[i] - (i + 1) % M + M) % M, pre = 1;
        for (int j = m - 1; j >= i; j--)
        {
            sum[i] = (sum[i] + C[m - 1][j] * S[j][i] % M * A[i] % M * pre % M) % M;
            pre = pre * a % M;
        }
    }
    if (m == 1)
    {
        sum[1] = 1;
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans = (ans + C[n][i] * (fpow(p[i] - 1 + M, m - 1) - sum[i] + M) % M * fpow(p[n - i], m - 1) % M) % M;
    }

    printf("%lld\n", ans);
    return 0;
}

C. Sum of Suffix Sums

Problem

初始时数组为空,要求执行 q q q 次操作,每次操作给定两个整数 t t t v v v,将数组中后 t t t 个元素删除并将 v v v 加入末尾。问每次操作后 ∑ i = 1 n s i \sum_{i=1}^n s_i i=1nsi 的大小, s i = a i + a i + 1 + ⋯ + a n s_i=a_i+a_{i+1}+\dots+a_n si=ai+ai+1++an

数据范围: 1 ≤ q ≤ 5 × 1 0 5 , 0 ≤ v ≤ 1 0 9 1\le q\le 5\times 10^5,0\le v\le 10^9 1q5×105,0v109

Soultion

直接用栈模拟即可,每加入一个元素 v v v,答案增加 v × s i z e v\times size v×size s i z e size size 表示加入后数组的大小,每删除一个元素 v v v,答案减少 v × s i z e v\times size v×size s i z e size size为删除前数组的大小。

时间复杂度: O ( q ) O(q) O(q)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
const ll M = 1000000007;
int q;
stack<ll> Q;
int main()
{
    cin >> q;
    ll ans = 0;
    for (int i = 1; i <= q; i++)
    {
        int t;
        ll v;
        cin >> t >> v;
        for (int i = 1; i <= t; i++)
        {
            ans = (ans - (ll)Q.size() * Q.top() % M + M) % M;
            Q.pop();
        }
        Q.push(v);
        ans = (ans + (ll)Q.size() * v) % M;
        printf("%lld\n", ans);
    }
    return 0;
}

H. World Finals

Problem

给定 n n n 支队伍在第 46 届比赛的队名 S S S,解决问题数量 p p p,罚时 t t t;以及 m m m 支队伍在第 47 届比赛的队名 S S S ,解决问题数量 p p p,罚时 t t t。如果两届比赛中出现相同的队名,那么该队可以选择参加第46届比赛也可以选择参加第47届,问队伍名为 l z r 010506 lzr010506 lzr010506 的最好的名次是多少。

数据范围: 1 ≤ n , m ≤ 1 0 5 , 1 ≤ ∣ S ∣ ≤ 10 , 1 ≤ p , t ≤ 1 0 9 1\le n,m\le 10^5,1\le|S|\le 10,1\le p,t\le 10^9 1n,m105,1S10,1p,t109

Soultion

分别对每届比赛进行排序,对于排名在 l z r 010506 lzr010506 lzr010506 之前的队伍,并且在两届比赛中都出现,那么可以让这支队伍去另一届比赛,这样就能使得 l z r 010506 lzr010506 lzr010506 的排名更靠前,最后对两届比赛的结果取个 m i n min min 即可。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
int n, m;
struct node
{
    string s;
    int p, t;
    node(string a, int b, int c)
    {
        s = a;
        p = b;
        t = c;
    }
};
bool cmp(node x, node y)
{
    return x.p == y.p ? x.t < y.t : x.p > y.p;
}
bool check(string s)
{
    string t = "lzr010506";
    if (s.size() != t.size())
    {
        return false;
    }
    for (int i = 0; i < s.size(); i++)
    {
        if (s[i] != t[i])
        {
            return false;
        }
    }
    return true;
}
int main()
{
    cin >> n;
    vector<node> p1, p2;
    map<string, int> mp;
    for (int i = 0; i < n; i++)
    {
        string s;
        int p, t;
        cin >> s >> p >> t;
        p1.push_back(node(s, p, t));
        mp[s]++;
    }
    cin >> m;
    for (int i = 0; i < m; i++)
    {
        string s;
        int p, t;
        cin >> s >> p >> t;
        p2.push_back(node(s, p, t));
        mp[s]++;
    }
    sort(p1.begin(), p1.end(), cmp);
    sort(p2.begin(), p2.end(), cmp);
    int ans1 = 1, ans2 = 1;
    for (int i = 0; i < n; i++)
    {
        if (check(p1[i].s))
        {
            break;
        }

        if (mp[p1[i].s] == 1)
        {
            ans1++;
        }
    }
    for (int i = 0; i < m; i++)
    {
        if (check(p2[i].s))
        {
            break;
        }

        if (mp[p2[i].s] == 1)
        {
            ans2++;
        }
    }
    printf("%d\n", min(ans1, ans2));
    return 0;
}

I. Mirror Maze

Problem

给定四种镜子“-”、“|”、“/”、“\”,的反射方式,以及 n × m n\times m n×m大小的镜子迷宫,现在有 q q q束光线,问每束光线能够反射的不同镜子数量。

数据范围: 1 ≤ n , m ≤ 1000 , 1 ≤ q ≤ 1 0 5 1\le n,m\le 1000,1\le q\le 10^5 1n,m1000,1q105

Soultion

利用光的可逆性就能很好的解决该问题,首先从外面射向镜子迷宫的光线一定可以从某个位置射出,那么可以先将这些光线进行预处理,处理过后,内部没有经过的点形成一定是一个环。因为如果内部的光线可以从某个位置射出迷宫,那么在之前预处理的时候就应该经过了,这说明内部的光线形成的是环。因此分两种情况预处理即可,具体实现细节看代码。

时间复杂度: O ( n m + q ) O(nm+q) O(nm+q)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
int n, m, q, f[1100][1100][4], loop;
string s[1100];
map<pair<char, int>, int> mp;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
struct node
{
    int x, y;
    int d;
    node(int a, int b, int c)
    {
        x = a, y = b, d = c;
    }
};
vector<node> Q;
set<pair<int, int>> st;
void dfs(int x, int y, int d)
{
    if (f[x][y][d] != -1)
    {
        return;
    }
    f[x][y][d] = st.size();
    Q.push_back(node(x, y, d));

    int D = mp[{s[x][y], d}];
    int ex = x + dx[D], ey = y + dy[D];
    if (ex >= 1 && ex <= n && ey >= 1 && ey <= m)
    {
        if (d != D)
        {
            st.insert({x, y});
        }
        dfs(ex, ey, D);
    }
}
int main()
{
    mp[{'|', 0}] = 0, mp[{'|', 1}] = 1, mp[{'|', 2}] = 3, mp[{'|', 3}] = 2;
    mp[{'-', 0}] = 1, mp[{'-', 1}] = 0, mp[{'-', 2}] = 2, mp[{'-', 3}] = 3;
    mp[{'/', 0}] = 3, mp[{'/', 1}] = 2, mp[{'/', 2}] = 1, mp[{'/', 3}] = 0;
    mp[{'\\', 0}] = 2, mp[{'\\', 1}] = 3, mp[{'\\', 2}] = 0, mp[{'\\', 3}] = 1;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
        s[i] = '#' + s[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            for (int k = 0; k < 4; k++)
            {
                f[i][j][k] = -1;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        st.clear();
        Q.clear();
        dfs(i, 1, 3);
    }
    for (int i = 1; i <= n; i++)
    {
        st.clear();
        Q.clear();
        dfs(i, m, 2);
    }
    for (int i = 1; i <= m; i++)
    {
        st.clear();
        Q.clear();
        dfs(1, i, 1);
    }
    for (int i = 1; i <= m; i++)
    {
        st.clear();
        Q.clear();
        dfs(n, i, 0);
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            for (int k = 0; k < 4; k++)
            {
                if (f[i][j][k] == -1)
                {
                    st.clear();
                    Q.clear();
                    dfs(i, j, k);
                    for (auto it : Q)
                    {
                        int x = it.x, y = it.y, d = it.d;
                        f[x][y][d] = st.size();
                    }
                }
            }
        }
    }
    cin >> q;
    for (int i = 1; i <= q; i++)
    {
        int u, v, d;
        string t;
        cin >> u >> v >> t;
        if (t[0] == 'a')
        {
            d = 1;
        }
        else if (t[0] == 'b')
        {
            d = 0;
        }
        else if (t[0] == 'l')
        {
            d = 3;
        }
        else
        {
            d = 2;
        }
        printf("%d\n", f[u][v][d]);
    }
    return 0;
}
  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值