河南萌新联赛2025第(二)场:河南农业大学补题报告

一、做题情况

ac:ADEIKM

未通过:BCFGHJL

赛后通过:BCGHL

二、个人赛后思考

1、个人数论知识掌握太少

2、太过于盲目跟榜,死磕于B题,最后还没写出来(C、G、H做过类似的,其中C、H个人认为有机会写出来)

三、题解报告

B题

链接:B-异或期望的秘密_河南萌新联赛2025第(二)场:河南农业大学
来源:牛客网
 

题目描述

给定正整数 l,r,y。从闭区间 [l,r]中随机且等概率地选择一个整数 x,计算 x⊕y(⊕ 表示按位异或)的二进制表示中 1 的个数的期望值。

答案需对10^9 + 7取模。

输入描述:

第一行,包含一个整数 T (1≤T≤2×10^5)。

接下来 T 行,每行包含三个整数 l,r,y(1≤l≤r≤10^9 , 1≤y≤10^9)

输出描述:

T 行,输出期望值对10^9 + 7取模的结果。

示例1

输入

复制

3
1 1 1
1 1 2
1 10 5

输出

复制

0
2
300000004

备注:

可以证明答案可以表示为一个不可约分数 p/q​,为了避免精度问题,请直接输出整数 (p×q^−1modM) 作为答案,其中 M = 10^9+7,q^-1 是满足 q×q^−1≡1(modM)的整数。
更具体地,你需要找到一个整数 x∈[0,M)满足 x×q对 M 取模等于 p,您可以查看样例解释得到更具体的说明。

做法:详细见代码

#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
const int mod = 1e9 + 7;
bool vis[32];//标记y的二进制中的1的位置
int cnt[32];//cnt[i]表示[0,r]中的二进制的第i位为1的数量
int quick_pow(int a, int b, int mod)
{
    int res = 1;
    a %= mod;
    while (b)
    {
        if (b % 2 == 1)
        {
            res = res * a % mod;
        }
        a = a * a % mod;
        b /= 2;
    }
    return res;
}
int f(int r, int y)
{
    for (int i = 0; i <= 31; i++)cnt[i] = 0;
    for (int i = 0; i < 31; i++)
    {
        int x = 1ll<<i;
        int t = 2 * x;
        int num = (r + 1) / t;
        int remain = (r + 1) % t;
        cnt[i+1] = (max(0ll, remain - x) + x * num % mod) % mod;//每个周期循环0都在1前面,比如i等于0时,循环周期为2,小于r的数的二进制的第i+1位(即第一位)依次为0 1 0 1...;i等于1时,循环周期为4,小于r的数的二进制的第i+1位(即第二位)依次为0 0 1 1 0 0 1 1...,由此max取0和remain-x的最大值
        if (num == 0 || (num == 1 && remain == 0))break;//及时退出,但本题数据量过小,不退出也可
    }
    int sum = 0;
    for (int i = 1; i <= 31; i++)
    {
        if ((vis[i]))sum = (sum + r + 1 - cnt[i] + mod) % mod;//y的二进制第i位为1,此时二进制第i位为0的数满足题意
        else sum = (sum + cnt[i]) % mod;//y的二进制第i位为0,需要第i位为1的数
    }
    return sum;
}
void solve()
{
    int l, r, y;
    cin >> l >> r >> y;
    int ny = quick_pow(r - l + 1, mod - 2, mod);
    int t = y;
    int pos = 1;
    memset(vis, 0, sizeof(vis));
    while (t)
    {
        if (t % 2 == 1)
        {
            vis[pos] = 1;
        }
        pos++;
        t /=2;
    }
    int res = (f(r, y) - f(l - 1, y) + mod) % mod;
    cout << res * ny % mod << endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
}

C题

链接:C-O神_河南萌新联赛2025第(二)场:河南农业大学
来源:牛客网
 

企鹅最近沉迷于游戏《O神》,他非常渴望获得限定角色"S柯克"。游戏抽卡系统规则如下:

1.每次抽卡消耗 x 原石

2.每次抽卡有 p/q​ 的概率直接获得S柯克

3.若连续 m−1 次未获得S柯克,则第 m 次必定获得

4.获得S柯克后保底计数重置

企鹅计划通过充值获取原石。游戏提供 nnn 种充值档位,每个档位花费 ai​ 元获得 bi 原石,且满足 ai​/bi​​≥1。此外,每种充值档位只能购买一次,但保证所有充值档位的原石总和至少为 x×m(即足够触发保底)。

企鹅能预知获得S柯克的所需要的抽数(预知能力并不影响抽卡概率),若他使用最优的充值方式使得花费最少,请问所有抽卡情况的获得S柯克的期望花费。

输入描述:

第一行:四个整数 x,p,q,m
(1≤x≤160,1≤p≤q≤100,1≤m≤100)

第二行:一个整数 n
(1≤n≤10)

接下来 n 行:每行两个整数 ai,bi
(1≤ai≤648,1≤bi≤6580)
保证所有 ∑i=1n​bi​≥x×m

输出描述:

一个整数:获得S柯克的期望花费(单位:元)
答案对10^9+7取模

示例1

输入

复制

1 1 2 2
1
1 2

输出

复制

1

说明

第一次就抽中的概率为1/2 第二次抽中的概率为1/2,抽一次最少要花1块钱,抽两次最少要花一块钱所以期望为1/2 * 1 + 1/2 * 1 = 1

示例2

输入

复制

1 1 2 2
2
1 1
1 1

输出

复制

500000005

说明

第一次就抽中的概率为1/2,第二次抽中的概率为1/2,抽一次最少要花费1块钱,抽两次最少要花费2块钱,期望为 1/2 * 1 + 1/2  * 2 = 3/2

做法:详细见代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int x, p, q, m, n;
int a[15];
int b[15];
int cost[105];//cost[i]表示抽i抽需要花费的钱
int dp[6505];//dp[i]表示花i元可获得的原石
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    T = 1;
    //cin >> T;
    while (T--)
    {
        const int mod = 1e9 + 7;
        cin >> x >> p >> q >> m >> n;
        for (int i = 1; i <= n; i++)cin >> a[i] >> b[i];
        dp[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 6500; j >= a[i]; j--)dp[j] = max(dp[j], dp[j - a[i]] + b[i]);
        }
        int t = 0;
        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= 6500; j++)
            {
                if (dp[j] >= i * x)
                {
                    cost[i] = j;
                    break;
                }
            }
        }
        int ny = quick_pow(q, mod - 2, mod);
        int res = 0;
        int t1 = (q - p) * ny % mod;
        int t2 = p * ny % mod;
        for (int i = 1; i <= m - 1; i++)res = (res + cost[i] * quick_pow(t1, i - 1, mod) % mod * t2 % mod) % mod;//注意只需循环到m-1次即可,第m次为保底,概率等于第m-1次的概率
        res = (res + cost[m] * quick_pow(t1, m - 1, mod) % mod) % mod;
        cout << res;
    }
}

链接:G-yume的灵机一动_河南萌新联赛2025第(二)场:河南农业大学
来源:牛客网

G题

题目描述

被迫营业的yume突然灵机一动有个想法:

给定一棵以 1 为根的树,包含 n 个节点,每个节点 i 有一个点权 ai。
现在需要选择 k 个叶子节点, 若一个非叶子节点的所有儿子均被选择,则该节点自动被选择。
请问如何选择,使得被选择的节点的点权之和的最大,输出最大值。

输入描述:

第一行输入两个整数 n,k:2≤n≤42000,保证 k 为正整数且不超过叶子节点个数 , k≤5500。
第二行输入 n 个整数 ai,表示第 i个节点的点权(0≤ai≤1000)。
接下来的 n−1 行分别输入两个数 u,v,表示 u 与 v 相连。

输出描述:

一个整数,表示最大值

示例1

输入

复制

5 2
1 2 5 2 2
1 2
1 3
2 4
2 5

输出

复制

7

说明

选择4 , 3节点即可

示例2

输入

复制

5 2
1 2 2 2 2
1 2
1 3
2 4
2 5

输出

复制

6

说明

选择4,5

做法:详细见代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, k;
int a[42005];
int dp[42005][5505];//dp[i][j]表示对于结点i选j个叶子结点的权值最大值
int siz[42005];//siz[i]表示结点i所拥有的叶子节点数量
vector<int>g[42005];
void dfs(int x, int farther)
{
    if (g[x].size() == 1)
    {
        dp[x][1] = a[x];
        siz[x] = 1;
        return;
    }
    for (int i = 0; i < g[x].size(); i++)
    {
        int t = g[x][i];
        if (t == farther)continue;
        dfs(t, x);
        siz[x] += siz[t];
    }
    if (siz[x] <= k)//第一种情况,该结点所拥有的叶子结点数量小于等于k,全选,只有这样此时dp[x][k]才是最大的
    {
        dp[x][siz[x]] += a[x];
        for (int i = 0; i < g[x].size(); i++)
        {
            int t = g[x][i];
            if (t == farther)continue;
            dp[x][siz[x]] += dp[t][siz[t]];
        }
    }
    int maxx = min(siz[x] - 1, k);//第二种情况,无法全选
    for (int i = 0; i < g[x].size(); i++)
    {
        int t = g[x][i];
        if (t == farther)continue;
        for (int j = maxx; j >= 1; j--)
        {
            for (int p = 1; p <= min(siz[t], k); p++)
            {
                if(j>=p)dp[x][j] = max(dp[x][j], dp[x][j - p] + dp[t][p]);
            }
        }
    }
}
void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)cin >> a[i];
    for (int i = 1; i <= n - 1; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, -1);
    cout << dp[1][k];
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int T = 1;
    //cin >> T;
    while (T--)
    {
        solve();
    }
}

链接:H-被无尽数字1环绕_河南萌新联赛2025第(二)场:河南农业大学
来源:牛客网

H题

题目描述

定义集合 S(x,y) 为:所有满足 0≤z≤x 且 z 的二进制表示中恰有 y 个 1 的整数 z 构成的集合。

给定整数 n,m,k,请你找到 S(n,m) 中的第 k 小的元素。若 S(n,m)中元素个数少于 k,则输出 −1。

输入描述:

第一行,包含一个整数 T (1≤T≤10^5)。

接下来 T 行,每行包含三个整数 n,m,k(1≤n,k≤10^18, 0≤m≤60)。

输出描述:

T 行,每行输出 S(n,m) 中的第 k 小元素;若不存在,则输出 −1。

示例1

输入

复制

10
10 3 1
10 3 2
10 0 1
10 0 2
10 4 1
100 10 10
10000 3 10
114514 19 1
9592783 4 24
7569921 4 50

输出

复制

7
-1
0
-1
-1
-1
28
-1
90
170

备注:

二进制表示中 1 的个数是指数字在二进制形式下含有的 1 的数量(例如,555 的二进制为 101,含 2 个 1,故 5∈S(x,2)当且仅当 x≥5x)。
集合 S(n,m) 中的元素按从小到大排序,第 k 小元素即排序后位置为 k(从 1 开始计数)的元素。

做法:详细见代码

#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
int n, m, k;
int dp[64][64][2];//dp[pos][cnt][free]表示处理到pos位,已选cnt个1,是否受限制(free)这种状态下后面满足题意的数的个数
vector<int>v;//储存n的二进制
int quick_pow(int a, int b, int mod)
{
    int res = 1;
    a %= mod;
    while (b)
    {
        if (b % 2 == 1)
        {
            res = res * a % mod;
        }
        a = a * a % mod;
        b /= 2;
    }
    return res;
}
int f(int pos, int cnt, int free)//数位dp板子
{
    if (pos == v.size())return (cnt == m) ? 1 : 0;
    if (dp[pos][cnt][free] != -1)return dp[pos][cnt][free];
    int maxx = free ? v[pos] : 1;
    int total = 0;
    for (int i = 0; i <= maxx; i++)
    {
        if (i == 0)total += f(pos + 1, cnt, free && (i == maxx));
        else total += f(pos + 1, cnt + 1, free && (i == maxx));
    }
    dp[pos][cnt][free] = total;
    return total;
}
int change(int n)
{
    v.clear();
    while (n)
    {
        v.push_back(n % 2);
        n /= 2;
    }
    if (n == 0)v.push_back(0);
    reverse(v.begin(), v.end());
    memset(dp, -1, sizeof(dp));
    return f(0, 0, 1);
}
void solve()
{
    cin >> n >> m >> k;
    if (change(n) < k)cout << -1 << endl;
    else
    {
        int res = 0;
        int cnt = 0;
        int free = 1;
        for (int pos = 0; pos < v.size(); pos++)
        {
            int maxx = free ? v[pos] : 1;
            int t = f(pos + 1, cnt, free && (0 == maxx));
            if (t >= k)free = free && (0 == maxx);//后面的满足题意的数字个数大于等于k,选择0
            else//选择1
            {
                cnt++;
                k -= t;
                free = free && (1 == maxx);
                res = res + quick_pow(2, v.size() - 1 - pos, LLONG_MAX);
            }
        }
        cout << res << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
}

链接:L-分块高手彻底怒了_河南萌新联赛2025第(二)场:河南农业大学
来源:牛客网

L题

题目描述

给定三个长度为 n 的数组 a,b,c,请计算以下三重求和表达式的值:



请将计算结果对 10^9 + 7 取模后输出。

输入描述:

第一行包含一个整数 n (1≤n≤10^5)。

第二行包含 n 个整数 a1,a2,…,an(1≤ai​≤10^9)。

第三行包含 n 个整数 b1,b2,…,bn (1≤bi​≤10^9)。

第四行包含 n 个整数 c1,c2,…,cn(1≤ci​≤10^9)。

输出描述:

一行,输出表达式的结果对 10^9 + 7 取模的值。

示例1

输入

复制

3
1 2 3
1 1 1
1 1 4

输出

复制

91

做法:题解给出了如下化简,将原式化简完的做法就显而易见了

#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
int a[100005];
int b[100005];
int c[100005];
int f[100005];//f[i]表示从i到nf函数的和
int g[100005];//g[i]表示从i到ng函数的和
const int mod = 1e9 + 7;
int quick_pow(int a, int b, int mod)
{
    int res = 1;
    a %= mod;
    while (b)
    {
        if (b % 2 == 1)
        {
            res = res * a % mod;
        }
        a = a * a % mod;
        b /= 2;
    }
    return res;
}
void solve()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];
    for (int i = 1; i <= n; i++)cin >> b[i];
    for (int i = 1; i <= n; i++)cin >> c[i];
    int res = 0;
    for (int i = 1; i <= n; i++)res = (res + i * (i + 1) / 2 * c[i]) % mod;//求后面部分的和
    for (int i = n; i >= 1; i--)f[i] = (f[i + 1] + n / i) % mod;
    for (int i = n; i >= 1; i--)
    {
        int ny = quick_pow(b[i], mod - 2, mod);
        g[i] = (g[i + 1] + (n / i) * ny % mod * f[i] % mod) % mod;
    }
    for (int i = 1; i <= n; i++)res = (res + a[i] * (n / i) % mod * g[i] % mod) % mod;
    cout << res;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int T = 1;
    //cin >> T;
    while (T--)
    {
        solve();
    }
}

四、赛后总结

数论知识需加强,不要盲目跟榜

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值