河南萌新联赛2025第(四)场:河南大学
链接:(8条未读私信) 河南萌新联赛2025第(四)场:河南大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
难度:
-
签到:A,C,G,J
-
简单:B,D,L
目录
A. 完美序列
思路分析:
实现代码:
C. 合并石子
思路分析:
实现代码:
G. 封闭运算
思路分析:
实现代码:
J. 故障机器人的完美牌组
思路分析:
实现代码:
B. 0!!!!!
思路分析:
实现代码:
L.故障机器人的完美遗物
思路分析:
实现代码:
A. 完美序列
思路分析:
-
统计数字出现次数:使用一个数组 cnt统计每个数字 x在序列 a中出现的次数 cnt[x]。
-
计算所有可能的和 s的覆盖次数:
-
遍历所有可能的数对 (i,j)(其中 i≤j),计算每个和 s=i+j的覆盖次数 sum[s]。
-
如果 i=j,则最多可以组成 cnt[i]/2个数对(每个数对需要两个相同的数字)。
-
如果 i !=j,则最多可以组成 min(cnt[i],cnt[j])个数对(取较小的那个数字的出现次数)。
-
-
将所有这些数对的贡献累加到 sum[s]中。
-
-
找到最大的覆盖次数:
-
遍历所有可能的和 s(从 2到 10000),找到最大的 sum[s]。
-
完美序列 b的最大长度为 2×max(sum),因为每个数对可以贡献 2 个元素。
-
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=2e5+2;
int a[N],sum[N];
void slove(){
int n,x;
cin>>n;
for(int i=1;i<=n;i++)
{cin>>x;a[x]++;
}for(int i=1;i<=5000;i++)
{for(int j=i;j<=5000;j++){if(i==j)sum[i+j]+=a[i]/2;elsesum[i+j]+=min(a[i],a[j]);}
}
int t=1;
for(int i=1;i<=10010;i++)
{t=max(t,sum[i]);
}
cout<<2*t;}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int _=1;//cin>>_;while(_--)slove();return 0;
}
C. 合并石子
思路分析:
根据样例可以知道合并石子必须得从两边向中间移动石子,使用左右指针l和r分别指向数组的首尾。每次选择当前石子数量较少的一端进行合并,这样可以尽量减少移动次数。每次移动a[i]个石子,每次最多移动k个,因此需要⌈a[i]/k⌉次(记得向上取整数)操作(即体力消耗)。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=2e5+4;
int a[N]; void slove(){int n,k; cin>>n>>k;for(int i=1;i<=n;i++) {cin>>a[i]; }int l=1,r=n,ans=0; // 初始化双指针l和r,ans记录体力消耗while(l<r) { // 当左右指针未相遇时循环if(a[l]<a[r]) { // 左侧石子较少a[l+1]+=a[l]; // 将左侧石子全部移到右侧相邻堆ans+=(a[l]+k-1)/k; // 计算移动所需体力(向上取整)l++; // 左指针右移}else { // 右侧石子较少或相等a[r-1]+=a[r]; // 将右侧石子全部移到左侧相邻堆ans+=(a[r]+k-1)/k; // 计算移动所需体力r--; // 右指针左移}}cout<<ans; // 输出最小体力消耗
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int _=1;//cin>>_;while(_--)slove();return 0;
}
G. 封闭运算
思路分析:
题目中说,若对于数组中任意的 i , j(1≤i≤j≤n)都存在一个 k,使得 a[ i ]∣a [ j ]=a[k] (其中 ∣ 表示按位或),则称该数组对于按位或运算是封闭的。所以我们只要找到存不存在就可以了。先把所有的数都存到一个哈希数组里面,方便查找,然后用两个for循环进行位运算,然后再数组里面找看存不存在运算后的数,只要有一个不存在,就不是封闭运算。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=101;
int a[N];
unordered_set<int> mp;
void slove(){
int n;
cin>>n;for(int i=0;i<n;i++)
{cin>>a[i];mp.insert(a[i]);
} for(int i=0;i<n;i++){for(int j=i;j<n;j++){int t=a[i]|a[j];if(mp.find(t)==mp.end()){cout<<"NO"<<endl;return ;}
}}
cout<<"YES"<<endl;}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int _=1;//cin>>_;while(_--)slove();return 0;
}
J. 故障机器人的完美牌组
思路分析:
【字典序】:从两个数组的第一个元素开始逐个比较,直到找到第一个不同的元素,较大元素所在的数组的字典序较大。如果比较到其中一个数组的结尾时依旧全部相同,则较短的数组字典序更小。
读取牌的数量 n
和第一张牌的威力 a[1]
。如果 n == 1
,直接输出这张牌的威力,因为无法进行交易。
寻找最大威力牌:初始化 mx
和 p
为0。遍历第2张到第n张牌,更新 mx
和 p
为当前最大值及其位置。
如果有多个最大值,选择最后面的那个(通过 >=
实现)。
全零情况处理:如果 mx == 0
,说明所有牌的威力都是0,无法通过交易增加字典序,直接输出所有牌。将第一张牌的威力 a[1]
加上最大威力牌 a[p]
的威力。将 a[p]
标记为-1(表示删除)。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=2e5+2;
int a[N];
void slove(){int n;cin>>n;cin>>a[1];if(n==1)//只有一张牌 {cout<<1<<endl<<a[1]<<endl;return ;}int mx=0,p=0;for(int i=2;i<=n;i++){cin>>a[i];if(a[i]>=mx)//当有多个最大的数字,选最后面的 {mx=a[i];p=i;}}if(a[p]==0)//输入的全是0的情况 {cout<<n<<endl;for(int i=1;i<=n;i++){cout<<a[i]<<' ';}return ;}a[1]+=a[p];a[p]=-1;cout<<n-1<<endl;for(int i=1;i<=n;i++){if(a[i]!=-1)cout<<a[i]<<' ';}
}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int _=1;//cin>>_;while(_--)slove();return 0;
}
B. 0!!!!!
思路分析:
这道题目要求计算区间[L, R]内所有整数的因数乘积末尾零的个数。关键在于理解末尾零的产生原理:每个零都由一对2和5的质因数相乘产生(10=2×5)。因此,我们需要统计所有因数乘积中2和5的质因数总数,并取两者的较小值作为答案。代码实现采用数论分块技术进行高效计算:首先定义calculate函数,通过遍历x的幂次(x, x², x³...)并使用数论分块统计区间内能被x的幂次整除的数的个数;然后在solve函数中分别计算区间内2和5的幂次总数,通过区间减法得到[L,R]范围内的统计结果,最终输出两者的最小值。
实现代码:
#include <iostream>
#include <algorithm> using namespace std;// 计算在[1, lim]范围内能被x的幂次整除的数的个数
long long calculate(long long x, long long lim) {long long num = 0; // 计数器,记录满足条件的数的个数// 遍历x的所有幂次:x, x^2, x^3,...直到超过limfor(long long i = x; i <= lim; i *= x) {long long n = lim / i; // 计算能被i整除的数的最大商// 使用数论分块优化计算for(long long l = 1; l <= n; ) {// 计算当前块的右边界long long r = n / (n / l); // 当前块内的数的个数乘以它们的商值num += (r - l + 1) * (n / l);// 移动到下一个块l = r + 1;}}return num;
}void solve() {long long L, R;cin >> L >> R; // 计算区间内能被2的幂次整除的数的个数long long a = calculate(2, R) - calculate(2, L - 1);// 计算区间内能被5的幂次整除的数的个数long long b = calculate(5, R) - calculate(5, L - 1);// 输出两者中的较小值cout << min(a, b) << '\n';
}int main() {ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1;// cin >> T; while(T--) {solve(); }return 0;
}
L.故障机器人的完美遗物
思路分析:
这道题目要求我们找出所有满足"完美遗物"条件的数字并求和。完美遗物的定义是:该数字的因数个数是一个质数且不等于2。通过分析可以发现,只有特定形式的数字才能满足这个条件。
观察到:完全平方数的因数个数是奇数,非完全平方数的因数个数是偶数,
因数个数为质数的完全平方数必须满足:该数可以表示为某个素数的偶数次幂(即p^(2m)),且2m+1是质数。
具体步骤:1.使用欧拉筛法预处理所有素数(标记在v数组中)
2. 预处理满足条件的数:遍历所有素数p,计算p的平方及更高次幂,检查幂次+1是否为质数,如果是,则标记该数的平方根(存储在vis数组中)。
3. 处理输入:对每个输入的数字,检查它是否是完全平方数,如果是,检查其平方根是否被标记为满足条件,累加所有满足条件的数字。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=1e6+2;
const int M=1e12; int a[N], prime[N]; // a存储输入数字,prime存储素数
bool v[N], vis[N]; // v标记是否为合数,vis标记满足条件的数
int ans=0; // 存储最终结果
int n, m, k; // n是输入数字个数,k是素数个数// 欧拉筛法求素数
void oula() {v[1] = 1; // 1不是素数for(int i=2; i<=N; i++) {if(!v[i]) prime[++k] = i; // i是素数,存入prime数组for(int j=1; j<=k && i*prime[j]<=N; j++) {v[i*prime[j]] = 1; // 标记合数if(i%prime[j]==0) break; // 关键优化:每个数只被最小素因子标记}}
}// 预处理满足条件的数
void pre() {for(int i=1; i<=k; i++) { // 遍历所有素数int p = prime[i]*prime[i]; // 计算素数的平方for(int j=2; ; j++) { // 计算更高次幂if(p > M) break; // 超过最大值则终止if(j+1<N && !v[j+1]) { // 如果j+1是素数int t = sqrtl(p); // 计算平方根if(t < N) vis[t] = 1; // 标记满足条件的数}p *= prime[i]; // 计算下一个幂次}}
}void slove() {cin >> n;for(int i=1; i<=n; i++) {cin >> a[i];}oula(); // 筛素数pre(); // 预处理满足条件的数for(int i=1; i<=n; i++) {int t = sqrtl(a[i]); // 计算平方根if(t*t == a[i] && vis[t]) { // 如果是完全平方数且满足条件ans += a[i]; // 累加到结果}}cout << ans;
}signed main() {ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int _=1;//cin >> _; while(_--) {slove(); }return 0;
}