河南萌新联赛2025第(二)场:河南农业大学(整除分块,二进制,树的搜索)
文章目录
- @[toc]
- A、约数个数和(整除分块)
- 思路
- 代码
- 扩展:取模(整除分块)
- 思路
- 代码
- B、异或期望的秘密
- 二进制位的周期性规律
- 核心思路
- 代码详细解释
- 1. 快速幂函数 qpow
- 2. 统计函数 count
- 3. 主逻辑 solve
- 完整代码
- D、开罗尔网络的备用连接方案
- 思路
- 建树
- 搜索
- 扩展:插排串联(树的搜索)
- 题目大意
- 思路
- 代码
- I、猜数游戏
- 代码
- K、打瓦
- 代码
- M、米娅逃离断头台
- 思路
- 代码
- 小结
文章目录
- @[toc]
- A、约数个数和(整除分块)
- 思路
- 代码
- 扩展:取模(整除分块)
- 思路
- 代码
- B、异或期望的秘密
- 二进制位的周期性规律
- 核心思路
- 代码详细解释
- 1. 快速幂函数 qpow
- 2. 统计函数 count
- 3. 主逻辑 solve
- 完整代码
- D、开罗尔网络的备用连接方案
- 思路
- 建树
- 搜索
- 扩展:插排串联(树的搜索)
- 题目大意
- 思路
- 代码
- I、猜数游戏
- 代码
- K、打瓦
- 代码
- M、米娅逃离断头台
- 思路
- 代码
- 小结
When the sharpest words wanna cut me down
I’m gonna send a flood, gonna drown them out
I am brave, I am bruised
I am who I’m meant to be, this is me ——《This Is Me》(the theme song of 《The Greatest Showman》)
这次比赛签到题没写出来,整的我都不敢往后开了。感觉是不是最近写题wa的太少了,碰到wa的题都不敢花时间改。还是上次的感受,题目的质量依旧非常的高,有几道模版题在之前都能看到他们的影子。上次我就花了一整天时间补题,但是感觉效率稍微有点低,真正汲取到的东西还不够,从这次开始要放更多重心在补题上。因为补的题都是可以够得上的,而且是真实会考而且已经考出来的东西,趁着比赛认真学习一下应该记忆会比较清晰。
A、约数个数和(整除分块)
来源:A-约数个数和_河南萌新联赛2025第(二)场:河南农业大学
思路
这个题其实还是非常简单的,主要分为两部分:第一部分:我们正常来想可能是分解为1:1;2:1,2;3:1,3····· 这样来思考。但是这样的复杂度就是n²。但是可以发现的是有很多因数都是重复的。如果我们按照因数来计算的话也许就会简化的多了。思考一下,1n的因数也可能是1n。举个例子,因数有2的数(也就是2的倍数)是每两个出现一次,而我们的左边界又是1,可以大胆的计算出因数2的个数就是n/2。因数有3的数(也就是3的倍数)是每三个出现一次。那么我们就可以大胆进行推测,在整个求和序列中因数x的出现次数就是 n/x 次。到了这里这一题就完成了一半了。第二部分:通过第一部分的归纳,我们可以简单将程序归纳为:
∑i=1n[ni]\sum_{i=1}^{n}\left [\frac{n}{i} \right] i=1∑n[in]
但是我们的n最大是2^31,大概就是2e9的范围,直接相加肯定是会超时的,此时就说到了我们的整除分块了。例如此时n=20:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
n/i | 20 | 10 | 6 | 5 | 4 | 3 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
我们会发现我们要相加的这些部分其实有许多是重复的,由于乘法向下取整的原因,只要不满足上一个边界那么就会掉到下一个数。如果我们直接求解出这一段数的左右边界的话就不用一个个相加了。从2-1的来枚举吧,根据数学知识就可以看出,当 i 被 n刚好整除的时候它对应的 i 就是这段数的右边界n/(n/i),如果再小就会向下取整了。左边界虽然直接不好求,但是这么来想:上一个区间右边界的下一个不就是此区间的左边界嘛。可以找出区间的长度那么每个不同的 n/i 就只用遍历一次就可以了。这样跳着求的话操作的次数就会极大的减少了。
其实这个也不算什么算法,只要对除法稍微有点了解就能推出来。我当时脑子非常的混乱,再加上签到题一直没过,写后边的题一直在想。这个题第一部分很快就想到了,后边一直把这一步和第一部分混在一起,整的非常复杂。如果状态好点的话我相信肯定可以把这个推出来的!
代码
void solve()
{cin >> n;for(int i=1; i<=n; i++){r=n/(n/i);//右边界就是刚刚好不会向下取整的那个点ans+=(n/i)*(r-l);//本区间的值✖️本区间的区间大小求本区间的总和l=r;//本次的右区间确定下一次的左区间i=l;//更新i值跳到下一个区间}cout << ans;
}
扩展:取模(整除分块)
来源:K-取模_2022年中国高校计算机大赛-团队程序设计天梯赛(GPLT)上海理工大学校内选拔赛
思路
需要先推导一下公式:
∑i=1n(n%i)=∑i=1n(n−⌊ni⌋×i)=n2−∑i=1n⌊ni⌋×i\sum_{i=1}^{n}(n \% i)=\sum_{i=1}^{n}\left(n-\left\lfloor\frac{n}{i}\right\rfloor \times i\right)=n^{2}-\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor \times i i=1∑n(n%i)=i=1∑n(n−⌊in⌋×i)=n2−i=1∑n⌊in⌋×i
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
[n/i]*i | 20 | 20 | 18 | 20 | 20 | 18 | 14 | 16 | 18 | 20 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
有没有发现什么规律?其实并没有规律😄最主要的还是处理[n/i],至于乘i可以求得此区间大小后用等差数列直接处理
需要注意的是:n*n会超long long,需要用_ _int128进行存储,中间还要格外注意别爆范围了
代码
void solve ()
{int l=0,r=0;cin >> n;m=n;__int128 sum=(__int128) m*m; // 强制转换为__int128 避免 n 较大时乘积溢出for (int l=1; l<=n; l++){r=n/(n/l);sum-=((__int128)(l+r)*(r-l+1)/2)*(n /l);l=r;}int ans=(sum % mod + mod) % mod;//别减成负数了cout << ans;
}
B、异或期望的秘密
来源:B-异或期望的秘密_河南萌新联赛2025第(二)场:河南农业大学
本题是一个有关二进制规律的题,也是比较模板,本题就来介绍一下。
可以结合本篇博客:关于二进制的规律-CSDN博客
二进制位的周期性规律
二进制位的变化具有周期性。以第 k
位为例:
- 周期长度为
2^(k+1)
。 - 在每个周期内,前
2^k
个数的第k
位为 0,后2^k
个数的第k
位为 1。
例如:
- 当
k=0
(最低位)时,周期长度为2^1=2
,序列为0,1,0,1,0,1,...
。 - 当
k=1
(次低位)时,周期长度为2^2=4
,序列为0,0,1,1,0,0,1,1,...
。 - 当
k=2
(第三位)时,周期长度为2^3=8
,序列为0,0,0,0,1,1,1,1,...
。
核心思路
- 数学期望分解: 二进制中每一位的贡献是独立的。对于第
k
位,其对总期望的贡献为 该位为 1 的概率。因此,总期望等于所有位的概率之和。 - 位运算规律:
x XOR y
的第k
位为 1,当且仅当x
的第k
位与y
的第k
位不同。- 若
y
的第k
位为 0,则x
的第k
位需为 1; - 若
y
的第k
位为 1,则x
的第k
位需为 0。
- 若
- 快速统计区间内位为 1 的数目: 使用
count
函数高效计算区间[0, x]
中第k
位为 1 的数的个数,利用二进制位的周期性规律,避免遍历整个区间。
代码详细解释
1. 快速幂函数 qpow
int qpow(int a, int b)
{int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod;b>>=1;}return ans;
}
- 功能:计算
a^b % mod
,用于求模逆元(根据费马小定理,a
在模mod
下的逆元为a^(mod-2)
)。 - 原理:快速幂算法,时间复杂度 (O(log b))。
2. 统计函数 count
int count(int k, int x)//求0~x区间内第k位有多少1
{int t=1LL<<(k+1);//第k位周期int full=(x+1)/t;//有多少个整周期(总数除周期长度)int re=(x+1)-t*full;//还剩下几个return full*t/2+max(0LL, re-t/2);//整周期中1的个数+剩余一段1的个数//例如k=2:周期为0,0,0,0,1,1,1,1·····//周期为8,可以观察到一个周期内前一半为0,后一半为1根据此规律进行计算
}
- 功能:计算区间
[0, x]
中第k
位为 1 的数的个数。 - 原理:
- 周期规律:第
k
位的变化周期为2^(k+1)
,每个周期前半部分为 0,后半部分为 1。 - 完整周期贡献:每个完整周期包含
t/2
个 1。 - 剩余部分处理:若剩余长度超过周期的一半,则贡献
re - t/2
个 1。
- 周期规律:第
3. 主逻辑 solve
void solve()
{cin >> l >> r >> y;ans=0;int len=r-l+1;int inv=qpow(len,mod-2);//因为要除以len,所以求一下它的逆元for(int k=0; k<=30; k++){int cnt1=count(k,r)-count(k,l-1);//(0~r)-(0~l-1)=(l~r)int cu=(y>>k)&1;//y的第k位是0 or 1?if(cu) cnt=len-cnt1;//为使异或值为1,需要反着来else cnt=cnt1; //如果y当前位为0就统计1的个数,不然就统计0的个数ans=(ans+cnt*inv)%mod;//加上当前位概率}cout << ans << endl;
}
- 输入处理:读取区间
[l, r]
和整数y
。 - 模逆元计算:由于期望计算需要除以区间长度
len
,在模运算中转换为乘以len
的逆元。 - 逐位计算:
- 统计区间内第
k
位为 1 的数目:通过count
函数差分得到[l, r]
中第k
位为 1 的数的个数cnt1
。 - 判断异或条件:根据
y
的第k
位的值,决定需要统计x
的第k
位为 0 还是 1 的数目。 - 累加贡献:将当前位的概率贡献
cnt/len
转换为模运算形式(cnt * inv) % mod
。
- 统计区间内第
完整代码
int qpow(int a, int b)
{int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod;b>>=1;}return ans;
}
int count(int k, int x)//求0~x区间内第k位有多少1
{int t=1LL<<(k+1);//第k位周期int full=(x+1)/t;//有多少个整周期(总数除周期长度)int re=(x+1)-t*full;//还剩下几个return full*t/2+max(0LL, re-t/2);//整周期中1的个数+剩余一段1的个数//例如k=2:周期为0,0,0,0,1,1,1,1·····//周期为8,可以观察到一个周期内前一半为0,后一半为1根据此规律进行计算
}
void solve()
{cin >> l >> r >> y;ans=0;int len=r-l+1;int inv=qpow(len,mod-2);//因为要除以len,所以求一下它的逆元for(int k=0; k<=30; k++){int cnt1=count(k,r)-count(k,l-1);//(0~r)-(0~l-1)=(l~r)int cu=(y>>k)&1;//y的第k位是0 or 1?if(cu) cnt=len-cnt1;//为使异或值为1,需要反着来else cnt=cnt1; //如果y当前位为0就统计1的个数,不然就统计0的个数ans=(ans+cnt*inv)%mod;//加上当前位概率}cout << ans << endl;
}
D、开罗尔网络的备用连接方案
来源:D-开罗尔网络的备用连接方案_河南萌新联赛2025第(二)场:河南农业大学
本题是一道相对模板的树的遍历,原来题目意思非常难懂(其实就是有问题),后来改了题面我也没看。其实还是比较基础的,刚好趁此机会学习一下树。
思路
只要按照题目的意思将整棵树给遍历一下就行了,在遍历的过程中记录此时的权值。主要是分为
建树
由于题中说的是要建无向树,所以可以用vector类型的数组进行存储,将两个点都互相存一下。
for(int i=1; i<n; i++)
{cin >> u >> v;t[u].push_back(v);//将两个点都互相存一下t[v].push_back(u);
}
搜索
对自己可以到达的点直接进行暴搜,注意别让它回头,不然会出现死循环。至于为什么初始的&值要赋为-1,这就要牵扯到二进制的一些知识了。
在二进制中,负数的表示并非简单地在正数前加符号,而是通过补码(Two’s Complement) 来实现,这是计算机中最常用的方式(目的是统一加减法运算)。
正数的补码就是它自己;
负数的补码:
- 先取对应正数的原码;
- 对原码按位取反(0 变 1,1 变 0),得到反码;
- 反码加 1,结果即为负数的补码。
对于7:
+7 | 0000 0111 | 正数原码 |
---|---|---|
-7 | 1111 1001 | ① 取反 11111000 → ② 加 1 得 11111001 |
那么对于-1:1的原码是0000 0001,① 取反 1111 1110 → ② 加 1 得 11111111
这样一来-1的二进制就全是1了,而&是收集0的个数,-1&x=x。
dfs(1,-1,0);//(主函数里)
void dfs(int x, int y, int z)//当前节点,之前的&值,上一个节点
{int c=a[x]&y;bitset<32> b(c);mp[b.count()]++;for(auto i:t[x]){if(i==z) continue;//避免再回去形成死循环dfs(i,c,x);}
}
扩展:插排串联(树的搜索)
来源:辽宁CCPC——插排串联
题目大意
大概的意思是说,有一个插座上连了一个插排,而插排之间又相互连接,最后连了用电器。给了每个插排最大的额定功率以及用电器的额定功率,让你判断是否可以通过一定的顺序交换使得整个东西可以正常运行。
思路
输入的时候可以直接把插排的额定功率都给存起来,然后通过搜索将所有插排的实际功率给存起来(插排的实际功率等于它所能到达的所有用电器的额定功率之和)。让他们分别进行排序,然后一一进行对照比较。如果有对照的双方实际功率大于额定功率的话那就一定不行,不然就可以。值得注意的是,整个系统所有的用电器额定功率之和不能超过2200W。
代码
// Problem: 插排串联
// Contest: Virtual Judge - Gym
// URL: https://vjudge.net/problem/Gym-105481C
// Memory Limit: 1024 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
int a[N];//存各个东西的额定功率
vector<int> q[N];//建树
map<int, int> mp;//存插排
vector<int> aa;//存实际功率
vector<int> bb;//存额定功率
int n;//节点个数
int sum;//存总功率
int dfs(int x)
{if(q[x].size()==0)//此时是叶子节点{sum+=a[x];return a[x];}int qq=0;for(auto i:q[x])//将它所连的用电器的额定功率加起来就是它的实际功率qq+=dfs(i);if (x!=0) // 非根非叶子节点aa.push_back(qq);return qq;
}
void solve()
{cin >> n;for(int i=1; i<=n; i++){int u,v;cin >> u >> v;q[u].push_back(i);//建树mp[u]++;//将插排存起来a[i]=v;//存该节点的额定功率}for(auto i:mp){if(i.fi==0) continue;//不算根节点bb.push_back(a[i.fi]);} dfs(0);//开始搜索找每个节点实际功率if(sum>2200){cout << "NO";return ;}sort(aa.begin(),aa.end());//排序后只要一一对应即可sort(bb.begin(),bb.end());for(int i=0; i<aa.size(); i++){if(aa[i]>bb[i])//实际功率不能大于额定功率{cout << "NO";return ;}}cout << "YES";
}
I、猜数游戏
这是一道签到题,能想到用二分,但是用几种形式都打了一遍交上去都不太对,后边就不想看了。没想到这么巧,感觉直接log有一点点猜的成分。
代码
void solve()
{cin >> n;int k=log2(n);if((int)pow(2,k)<n) cout << k+1;else cout << k;
}
K、打瓦
签到题,直接输出。
代码
void solve()
{cin >> s;cout << "gugugaga";
}
M、米娅逃离断头台
来源:M-米娅逃离断头台_河南萌新联赛2025第(二)场:河南农业大学
思路
这是一道简单的数学题,经过简单的公式推导就出来了,记得用double,保留两位小数。
设小圆半径为a,大圆半径为b。则可得推导式:
x=2b2−a2x=2\sqrt{b²-a²} x=2b2−a2
可以推导出阴影面积的公式S:
S=π(b2−a2)2=π(x2)8S=\frac{\pi(b²-a²)}{2} =\frac{\pi(x²)}{8} S=2π(b2−a2)=8π(x2)
代码
void solve()
{cin >> k;double dou=(k/2)*(k/2);double pai=dou*3.1415926535/2;cout << fixed << setprecision(2) << pai;
}
小结
花了好长时间来写这篇博客,可以说是知识最密集的一篇了。第一次学习之后记忆肯定非常的浅,后边还要来复习啊~