河南萌新联赛2025第(二)场:河南农业大学_题解
本场榜单歪了,F题定位最难的那档但是被很多大佬过掉了。
C、D题题面难以理解也给大家带来了不好的体验。
这场有太多数学题,预估难度较低,但是实际看下来难度高了。
抱歉给大家带来了不好的参赛体验。
预估难度:
-
简单:K I M D
-
中等:E C B A L
-
较难:H G
-
困难:J F
A 约数个数和
数论分块求和即可
#include "bits/stdc++.h"
#define endl '\n'
using ll=long long;
using namespace std;
int main(){
int n;
cin>>n;
ll ans=0;
for(int i=1,j;i<=n;i=j+1){
j=n/(n/i);
ans+=(j-i+1ll)*(n/i);
}
cout<<ans<<endl;
return 0;
}
B 异或期望的秘密
定义函数 表示在区间
中第
位为 1 的数的个数。
那么区间 中第
位为 1 的数目为:
若 的第
位为 1,则贡献为区间中该位为 0 的数的比例:
若 的第
位为 0,则贡献为区间中该位为 1 的数的比例:
将所有二进制位的贡献相加,得到期望值。
#include"bits/stdc++.h"
#define endl '\n'
using ll=long long;
using namespace std;
constexpr int N=1e5+10;
struct Mint{...};
int count(int k,int X){
return (X+1)/(1<<k+1)*(1<<k)
+max(0,(X+1)%(1<<k+1)-(1<<k));
}
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
int T;
cin>>T;
while(T--){
int l,r,y;
cin>>l>>r>>y;
Mint ans;
for(int k=30;k>=0;k--){
if(y>>k&1)
ans+=(Mint(r-l+1)-(count(k,r)-count(k,l-1)))/(r-l+1);
else
ans+=Mint(count(k,r)-count(k,l-1))/(r-l+1);
}
cout<<ans<<endl;
}
return 0;
}
C O神
先用背包处理花 元最多能得到多少原石
,再计算出抽
抽最少要充值多少钱
,然后算出第
抽抽出的概率
,答案就是
。
#include "bits/stdc++.h"
#define endl '\n'
using ll = long long;
using namespace std;
#define int long long
constexpr int mod = 1e9 + 7;
ll qpow(ll a, ll b, ll p)
{
a %= p;
ll ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
int a[200];
int b[200];
int c[200];
void solve()
{
int n, p, q, m, x;
cin >> x >> p >> q >> m >> n;
for (int i = 1; i <= n; i++)
cin >> a[i] >> b[i];
vector<int> dp(20001, 0);
dp[0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 20000; j >= a[i]; j--)
{
dp[j] = max(dp[j], dp[j - a[i]] + b[i]);
}
}
int now = 0;
for (int i = 1; i <= m; i++)
{
while (dp[now] < x * i)
now++;
c[i] = now;
}
int ans = 0;
int p1 = p * qpow(q, mod - 2, mod) % mod;
int q1 = ((1 - p1) % mod + mod) % mod;
now = 1;
for (int i = 1; i < m; i++)
{
ans += (c[i] * p1 % mod * now % mod);
ans %= mod;
now *= q1;
now %= mod;
}
ans += (now * c[m] % mod);
ans %= mod;
cout << ans << endl;
}
signed main()
{
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}
D 开罗尔网络的备用连接方案
以1为根节点dfs遍历这棵树每经过一个节点就记录其二进制1的个数
#include "bits/stdc++.h"
#define endl '\n'
using ll = long long;
using namespace std;
#define int long long
vector<int> arr[100001];
int a[100001];
int cnt[60];
void dfs(int u, int fa, int now)
{
int cc = 0;
for (int i = 0; i <= 32; i++)
{
if (now >> i & 1)
cc++;
}
cnt[cc]++;
for (auto v : arr[u])
{
if (v != fa)
{
dfs(v, u, now & a[v]);
}
}
}
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
arr[u].push_back(v);
arr[v].push_back(u);
}
dfs(1, 0, a[1]);
while (m--)
{
int x;
cin >> x;
cout << cnt[x] << endl;
}
}
signed main()
{
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}
E 咕咕嘎嘎!!!(easy)
本题数据较小,有多种解法。
说一个比较容易理解的dp做法
设 为选了
个数他们
为
的方案数,那么我们就可以用类似背包的转移来解决这道题。
#include"bits/stdc++.h"
using namespace std;
constexpr int N=5e3+10,mod=1e9+7;
int dp[N][N];
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
(dp[i][1]+=1)%=mod;
for(int g=1;g<i;g++){
for(int j=m;j>=2;j--){
(dp[gcd(g,i)][j]+=dp[g][j-1])%=mod;
}
}
}
int ans=0;
for(int g=2;g<=n;g++)
(ans+=dp[g][m])%=mod;
cout<<ans;
return 0;
}
F 咕咕嘎嘎!!!(hard)
定义 为选定的
个数的最大公因数恰好为
的方案数,
为选定的
个数都是
的倍数的方案数。
等价于选定的
个数的最大公因数是
的倍数的方案数。
由定义得:
显然, 。由莫比乌斯反演得:
,即
。
那么
预处理莫比乌斯函数和组合数后,即可 时间复杂度得到答案。
然而, 可能会因为常数过大难以通过本题。充分发扬人类智慧,上界可以从
变成
(大于此值的最大公约数不可能出现,因为这样的数不存在
个),特判
的情况,把莫比乌斯的求解常数降低至少一半。
#include"bits/stdc++.h"
#define endl '\n'
using ll=long long;
using namespace std;
constexpr int N=4e7+10,mod=1e9+7;
vector<int>p;
bool flg[N];
int mu[N],pmu[N];
void getmu(int n){
mu[1]=1;
for(int i=2;i<=n;i++){
if(!flg[i]){
p.push_back(i);
mu[i]=-1;
}
for(int pj:p){
if(i*pj>n)
break;
flg[i*pj]=true;
if(i%pj==0){
mu[i*pj]=0;
break;
}
mu[i*pj]=-mu[i];
}
}
for(int i=1;i<=n;i++)
pmu[i]=pmu[i-1]+mu[i];
}
int fact[N],inv[N];
ll qpow(ll a,ll b,ll p){
a%=p;
ll ans=1%p;
for(;b;b>>=1){
if(b&1)
ans=ans*a%p;
a=a*a%p;
}
return ans;
}
void init(){
fact[0]=1;
inv[0]=1;
for(int i=1;i<N;i++)
fact[i]=(ll(i)*fact[i-1])%mod;
inv[N-1]=qpow(fact[N-1],mod-2,mod);
for(int i=N-2;i>=1;i--){
inv[i]=inv[i+1]*(i+1ll)%mod;
}
}
ll C(ll n,ll r){
if(r>n||r<0)
return 0;
if(n==r||r==0)
return 1;
return (ll(fact[n])*inv[r]%mod*inv[n-r]%mod);
}
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
init();
int n,m;
cin>>n>>m;
if(m==1){
cout<<n-1;
return 0;
}
getmu(n/m);
ll ans=0;
for(int i=2;i<=n/m;i++)
ans=(ans-(mu[i]*C(n/i,m)%mod)+mod)%mod;
cout<<ans;
return 0;
}
G yume的灵机一动
联想树形dp, 表示 第
个节点选择
个叶子节点的最大价值,枚举
的子节点
,设在
节点中选取
个叶子节点有转移方程:
易知时间复杂度为 ,发现可以使用树上背包优化,因此题目实际复杂度为
。
#include"bits/stdc++.h"
#define endl '\n'
using ll=long long;
using namespace std;
constexpr int N=42000+4,M=5500+4;
constexpr int inf=0X3F3F3F3F;
constexpr ll INF=0X3F3F3F3F3F3F3F3F;
int n,k;
int dp[N][M],siz[N],a[N];
vector<int>g[N];
void dfs(int u,int p){
if(u!=1&&g[u].size()==1){
siz[u]++;
dp[u][1]=a[u];
return;
}
for(auto v:g[u]){
if(v==p)
continue;
dfs(v,u);
for(int i=min(siz[u],k);i>=0;i--){
for(int j=0;j<=siz[v]&&i+j<=k;j++){
dp[u][i+j]=max(dp[u][i+j],dp[u][i]+dp[v][j]);
}
}
siz[u]+=siz[v];
}
if(siz[u]<=k){
dp[u][siz[u]]+=a[u];
}
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
cout<<dp[1][k]<<endl;
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
solve();
return 0;
}
H 被无尽数字1环绕
对于 “ 范围内二进制下
的个数为
的数有多少个 ” 这个问题,可以通过类似数位dp的思想来解决。那么找到最小的数
使得 “
范围内二进制下
的个数为
的数有
个 ”,这个
即为答案,可以使用二分查找求解。
“ 范围内二进制下
的个数为
的数有多少个 ”:在二进制下从高位到低位遍历:
位之前的位数的值跟
保持一致,
的第
位为
时,可以将这一位设置为
,那么后续的位数就可以随意设置,利用组合数求解贡献。假设后续有
位,
之前的位数有
个
,那么当前造成的贡献就是
。注意,这个方法算的是小于
的贡献,需要把
的贡献加上。
#include <bits/stdc++.h>
#define endl '\n'
using ll = long long;
using namespace std;
ll n,m,k;
ll C[61][61];
bool check(ll lim){
int cnt=0;
ll res=0;
for(int i=60;i>=0;i--){
if(lim>>i&1){
if(cnt<=m)
res+=C[i][m-cnt];
cnt++;
}
}
if(cnt==m)
res++;
return res>=k;
}
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
for(int i=0;i<=60;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
int t;
cin>>t;
while(t--){
cin>>n>>m>>k;
ll l=0,r=n+1;
while(l<r){
ll mid=(l+r)/2;
if(check(mid))
r=mid;
else
l=mid+1;
}
cout<<(l==n+1?-1:l)<<endl;
}
return 0;
}
I 猜数游戏easy
通过每次猜数得到的大小关系我们可以不断二分去缩短区间,所以答案就是二分的次数
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ll n;
cin >> n;
for(int i=0;i<=64;i++)
{
if((1ll<<i)>=n)
{
cout<<i<<endl;
break;
}
}
return 0;
}
J 猜数游戏hard
假设我们猜测的数字是 ,那么答案数字
如果落入区间
都算我们猜测成功。
这启发我们可以把值域划分成一段段区间,区间中心点掌管这个区间答案。
区间 猜
,区间
猜
构造数组 :
,其中
是每个区间左端点,直到
,构造完毕。
这样对于每个 所对应区间,我们只需要猜
即可,对数组二分即可达到最优猜测,答案就是二分次数。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ll n, k;
cin >> n >> k;
// 【1,k*k】猜k
// 【k*k+1,(k*k+1)*k*k】猜(k*k+1)*k
// 【(k*k+1)*k*k+1】
/*
a1=1;
ai=k*k*ai-1+1
y=k*ai
*/
ll cnt = 1;
__int128_t res = 1;
for (int i = 1; i <= 64; i++)
{
res = res*k * k+ 1;
if (res > n)
break;
cnt++;
}
if(k==1)
cnt=n;
// cout<<cnt<<endl;
for (int i = 0; i <= 64; i++)
{
if ((1ll << i) >= cnt)
{
cout << i << endl;
break;
}
}
return 0;
}
K 打瓦
直接输出gugugaga即可
#include"bits/stdc++.h"
using namespace std;
int main(){
cout<<"gugugaga";
return 0;
}
L 分块高手彻底怒了
本题出题人初始的解法需要用到数论分块,后来发现不需要。如果不是忘记之前的解法,出题人可能不会发现简单做法,这也是题目标题的由来(笑)。
以下为简单做法题解
定义
则
定义
则
在 的时间复杂度内即可求出答案,
是求模意义下乘法逆元的复杂度。
#include"bits/stdc++.h"
using ll=long long;
using namespace std;
constexpr int N=1e5+10;
struct Mint{...};
Mint a[N],b[N],c[N];
Mint f[N],g[N];
int main(){
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];
Mint ans=0;
for(int k=1;k<=n;k++){
ans=(ans+k*(k+1ll)/2*c[k]);
}
for(int i=n;i>=1;i--){
f[i]=f[i+1]+(n/i);
}
for(int i=n;i>=1;i--){
g[i]=g[i+1]+(1/b[i])*(n/i)*f[i];
}
for(int i=1;i<=n;i++){
ans=ans+g[i]*a[i]*(n/i);
}
cout<<ans<<'\n';
return 0;
}
M 米娅逃离断头台
设大圆半径为 小圆半径为
从圆心连向切线要求的面积为
,又勾股定理可得
等于
,固答案为
。
#include"bits/stdc++.h"
using namespace std;
int main(){
int x;
scanf("%d",&x);
double ans;
ans=((x*x*1.0)/4.0)*acos(-1)/2.0;
printf("%.2lf",ans);
return 0;
}