集训1 20250122
集训1 20250122
牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
A:
题目大意:给出一个序列,找到一个不大于 \(2^{18}\) 的正整数 \(x\),使得 \(x\) 与序列中的任何数都不互为倍数关系
#include<bits/stdc++.h> using namespace std; int a[100010]; bool judge(long long x ){ if (x==2) return 1; for (int i=2;i<=x/i;i++){ if (x%i==0) return 0; } return 1; } void solve(void){ int n; cin>>n; for (int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+n+1); if (a[1]==1){ cout<<-1<<endl; return ; } for (long long i=1;i<=1e9;i++){ if (judge(a[n]+i)){ cout<<a[n]+i<<endl; return; } } } int main() { int T; cin>>T; while (T--) solve(); return 0; }
显然,序列中有 \(1\) 时,不可能存在 \(x\),对序列排序后,从最大的元素开始向后枚举,直到找到一个质数即可
偷懒:直接输出一个较大的质数 \(1e9+7\) 即可
B:
题目大意:给出一个 \(n\) 个节点的树,和 \(n-1\) 条边,判断能否找到一条简单路径经过所有节点(简单路径:一条路径,其经过的顶点和边互不相同)
#include<bits/stdc++.h> using namespace std; map<int,int> m; int main(){ int n; cin >> n; for(int i = 1; i < n; i++){ int u, v; cin >> u >> v; m[u]++; m[v]++; } vector<int> ans; for(auto [x,y]:m){ if(y>2){ cout<<-1; return 0; } if (y==1) ans.push_back(x); } if (ans.size()==2) cout<<ans[0]<<' '<<ans[1]; else cout<<-1; return 0; }
一开始当图来处理了,绕了一圈也算做对(多判断了重边)
这是一颗树,所以说所有的节点都是能够连通的,又因为边数加一等于节点数,所以能够找到简单路径的树,必然是一条链
设置一个 map
来存每个节点的度数,端点的度数为 \(1\),非端点的度数为 \(2\)
存完所有边后,遍历一遍 map
,找到度数为 \(1\) 的节点,即为需要输出的端点,注意端点只有两个,可以判断一下是否成立
判断重边:
set<pair<int,int>> se; se.insert({min(u,v),max(u,v)}); if (se.size()!=n-1){ cout<<-1; return 0; }
D:
题目大意:给出一个序列,判断是否为双生数组(双生数组:数组大小为偶数,数组的元素种类恰好为 \(2\) 种,且这两种元素的出现次数相同)
#include<bits/stdc++.h> using namespace std; map<int,int> m; void solve(void){ m.clear(); int a,n; cin>>n; for (int i=1;i<=n;i++){ cin>>a; m[a]++; } if (n%2!=0){ cout<<"No"<<endl; return; } for (auto [x,y]:m){ if (y!=n/2){ cout<<"No"<<endl; return; } } cout<<"Yes"<<endl; return; } int main() { int T; cin>>T; while (T--) solve(); return 0; }
简单模拟,利用 map
存元素个数,最后遍历即可、
G:
题目大意:给定一个数组,每次可以选取其中任意两个数分别进行 \(+1,-1\) 操作,求这个数组能否变成一个排列,能的话输出最小操作数
(排列:长度为 \(n\) 的排列是由 \(1\)~\(n\) 这 \(n\) 个整数,按照任意顺序组成的数组,每个整数恰好出现一次)
#include<bits/stdc++.h> using namespace std; long long a[100010]; int main() { long long n; cin>>n; for (int i=1;i<=n;i++) cin>>a[i]; long long sum=0; sort(a+1,a+n+1); for (int i=1;i<=n;i++) sum+=a[i]; if (sum!=(n+1)*n/2) cout<<-1; else{ long long res=0; for (int i=1;i<=n;i++){ res+=abs(a[i]-i); } cout<<res/2; } return 0; }
因为同时 \(+1,-1\) 不改变整体的和,所以需要判断这个数组元素的和是否为 \(\sum_{i=1}^n i\)
然后根据贪心,对排序后的数组进行操作,计算 \(\sum_{i=1}^n \lvert a_i-i\rvert\),因为加减一的次数都是相同的,最后输出 \(res/2\) 即可
又或者:
不计算 \(\sum_{i=1}^n i\),只需要贪心记录 \(sum=\sum_{i=1}^n a_i-i\) ,因为加减一的次数相同,所以能形成排列的数组的 \(sum=0\)
for(int i = 1; i <= n; i++){ sum += a[i] - i; if(a[i] < i) ans += i - a[i];//ans记录加一的操作数 } if(sum) ans = -1; cout << ans << endl;
E:
题目大意:给出一个元素个数为偶数的数组,每次可以选取一个元素进行 \(+1\) 或 \(-1\) 操作,给出将这个数组变为双生数组的最少操作数
#include<bits/stdc++.h> using namespace std; int a[100010]; int n; long long calc(int x,int y){ long long suma=0,sumb=0; for (int i=1;i<=n/2;i++) suma+=abs(a[i]-x); for (int i=n/2+1;i<=n;i++) sumb+=abs(a[i]-y); return suma+sumb; } void solve(void){ cin>>n; for (int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+n+1); int x,y; int t=n/2; if (t%2==0){ x=a[t/2]+a[t/2+1]>>1; y=a[t/2+t]+a[t/2+t+1]>>1; }else{ x=a[t/2+1]; y=a[t/2+t+1]; } long long ans=0; if (x==y) ans=min(calc(x-1,y),calc(x,y+1)); else ans=calc(x,y); cout<<ans<<endl; return; } int main() { int T; cin>>T; while (T--) solve(); return 0; }
首先按照大小顺序把数组分成两个部分,然后分别计算前后部分的中位数 \(x,y\) ,这个 \(x,y\) 即为双生数组的两个元素
为什么需要找中位数,这是因为对于每个 \(x,y\) ,如果要变为双生数组,那么需要的功为:
上面的表达式可以看作每个 \(a_i\) 分别到 \(x,y\) 的距离之和,当且仅当 \(x,y\) 为 \(a_i\) 的中位数时,距离和最小,中位数平衡了数组的分布
如果 \(x=y\),那么就需要将 \(x-1\) 与 \(y+1\) 进行比较,得到最小值即可
1 2 3 4 4 4 4 4 4 4 4 5 6 7 4-1=3 4+1=5
H:
题目大意:依次给出 \(n\) 个区间 \([l_i,r_i]\),每个 \(a_i\) 的取值范围在 \([l_i,r_i]\) 内,判断 数组\(a\) 能否构成一个排列,能的话输出 \(a_i\)
#include<bits/stdc++.h> using namespace std; int ans[100010]; int main() { int n; cin>>n; int cnt=1; deque<array<int,3>> dq(n); set<array<int,3>> st; for (auto &[l,r,i]:dq){ cin>>l>>r; i=cnt++; } sort(dq.begin(),dq.end()); for (int x=1;x<=n;x++){ while (dq.size()&&dq[0][0]<=x){ auto [l,r,i]=dq[0]; st.insert({r,l,i}); dq.pop_front(); } if (st.empty()){ cout<<-1; return 0; } auto [r,l,j]=*st.begin(); st.erase(st.begin()); if (r<x){ cout<<-1; return 0; } ans[j]=x; } for (int i=1;i<=n;i++) cout<<ans[i]<<' '; return 0; }
贪心的思想,在区间右端点越小的情况下选取 \(a_i\) 越有利
利用 deque
读入区间和区间的编号(便于记录答案)
sort deque
默认以 l,r,i
的顺序进行从小到大的排序,之后遍历需要的 \(x\)
如果deque
中还有区间并且这个区间的 l
是小于 \(x\) 的(有解),那么就按照 [r,l,i]
的顺序放入set
里面
set
按照r
从小到大排序,取越小的越有利
遍历deque
后,如果 set
里面没有区间,说明不存在合理的解,直接退出
记录答案时,按照 set
记录的区间编号存储答案,要是 r<x
说明无解,直接退出
J:
题目大意:给定一个数组,从其中任取两个元素 \(a_i,a_j(i<j)\) 满足 \(a_i\oplus a_j=gcd(a_i,a_j)\) 的方案数有多少
#include<bits/stdc++.h> using namespace std; map<int,int> m; int main() { int n; cin>>n; int a; for (int i=1;i<=n;i++){ cin>>a; m[a]++; } long long ans=0; for (int p=1;p<=2e5;p++){//枚举因子p for (int j=p;j<=2e5;j+=p){//枚举以p为因子的数 if (__gcd(j,p^j)==p&&j<=2e5) ans+=1ll*m[j]*m[p^j]; } } cout<<ans/2; return 0; }
设 \(p=gcd(a_i,a_j)\) 为 \(a_i\) 的一个因子,那么就有
所以枚举 \(p\) ,然后依次判断 \(kp=a_i\) 在 \(p=gcd(a_i,a_i\oplus p)\) 下的正确性
利用 \(ans=t_{a_i}*t_{a_j}\) ,$t_k $ 表示 \(k\) 在数组中出现的次数,计算答案
又因为 \(a,b\) 与 \(b,a\) 算一个组合,所以最后的答案数需要除以二
M:
题目大意:给定一个数组,可以选择一个非空区间,对其中的元素都乘上 \(2\) ,求操作后的数组的最小极差
#include<bits/stdc++.h> using namespace std; pair<int,int> a[100010]; int num[100010]; int main() { int n; cin>>n; if (n==1){//特判 cout<<0; return 0; } for (int i=1;i<=n;i++){ cin>>a[i].first; a[i].second=i; num[i]=a[i].first; } sort(a+1,a+1+n); a[n+1].first=2e9;//预设极限值 int ans; if (2*a[1].first<a[n].first){ ans=a[n].first-min(2*a[1].first,a[2].first); } else{ ans=2*a[1].first-a[2].first; } int l=a[1].second,r=a[1].second; int maxm=max(2*a[1].first,a[n].first); for (int i=2;i<=n;i++){ while(a[i].second<l){ l--; maxm=max(2*num[l],maxm); } while(a[i].second>r){ r++; maxm=max(2*num[r],maxm); } ans=min(ans,maxm-min(2*a[1].first,a[i+1].first)); } cout<<ans; return 0; }
非空区间即至少有一个元素乘以二倍,基于贪心的思想,对数组排序,并且用 pair
记录编号
考虑三种情况:
ans=max-2*min
,极差仍然为原来数组的最大值减去乘了二倍的最小值ans=2*min-l_min
,乘了二倍的最小值变为当前数组的最大值,答案为当前的最大值减去当前的最小值(原来的次小值)ans=max-l_min
,极差为原来的最大值减去当前的最小值(原来的次小值)
可以看出,需要维护两个集合,一个集合用来记录乘了二倍的元素,另一个用来记录还没有乘的元素
int ans; if (2*a[1].first<a[n].first){ ans=a[n].first-min(2*a[1].first,a[2].first); } else{ ans=2*a[1].first-a[2].first; } int l=a[1].second,r=a[1].second; int maxm=max(2*a[1].first,a[n].first);
首先对原来数组的最小值进行乘 \(2\) 操作,分别考虑三种情况下的极差,将需要乘 \(2\) 的集合的左右端点定位到最小值元素的编号上
贪心地按照原来数组从小到大的顺序进行区间扩充操作
while(a[i].second<l){ l--; maxm=max(2*num[l],maxm); } while(a[i].second>r){ r++; maxm=max(2*num[r],maxm); } ans=min(ans,maxm-min(2*a[1].first,a[i+1].first));
当前枚举的 \(i\) 如果在 \([l,r]\) 内,那么就不用更新当前数组的最大值
如果不在区间内,则需要移动左右端点到 \(i\) 的编号上,然后更新当前数组的最大值
最后的答案更新为 当前数组的最大值减去当前数组的最小值(原来数组的次小值或原来数组最小值的两倍)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具