集训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\) ,如果要变为双生数组,那么需要的功为:

\[\sum_{i=1}^{\frac{n}{2}} \vert a_i-x\rvert+\sum_{i=\frac{n}{2}+1}^n \lvert a_i-y\rvert \]

上面的表达式可以看作每个 \(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\) 的一个因子,那么就有

\[a_i\oplus p=a_j\implies p=gcd(a_i,a_i\oplus p) \]

所以枚举 \(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\) 的编号上,然后更新当前数组的最大值

最后的答案更新为 当前数组的最大值减去当前数组的最小值(原来数组的次小值或原来数组最小值的两倍)

posted @   才瓯  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示