一、做题情况
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=1nbi≥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();
}
}
四、赛后总结
数论知识需加强,不要盲目跟榜