2025牛客寒假算法基础集训营第一场补题
目录
J-硝基甲苯之袭
题意:
给定一个数组 a,问存在多少对 (i,j) 满足 a[i] xor a[j] = gcd(a[i], a[j])。
题解:
-
对于两个数的 gcd,gcd 一定是这两个数的因子,所以去遍历其中一个数的因子,用 x 以及它的因子 p 来表示 y,即 y=x^p,代入到式子,看知否成立
-
为啥呢?
-
X xor Y = gcd(X, Y),令 P 表示 gcd,我们知道,P 一定是 X 的因子,所以 X xor Y = P,又因为异或满足 Y = X xor P,且 A xor A = 0,所以原式变成:P = gcd(X, X^P),如果数组里有 Y=X^P 这个数,就算上。
-
时间复杂度 O(n 根号n)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int t, n, m;
int gcd(int a, int b)
{
return b==0?a:gcd(b,a%b);
}
void solve()
{
int n;
cin >> n;
vector<int> a(n);
map<int,int> mp;
for (int i = 0; i < n;i++){
cin >> a[i];
mp[a[i]]++;
}
int ans = 0;
for(auto it:mp)
{
for(int j=1;j*j<=it.first;j++)//找因子
{
if(it.first%j==0)
{
int y1=it.first^j;
if(y1>it.first && j==gcd(it.first,y1))
ans+=mp[it.first]*mp[y1];
if(j*j!=it.first)//另一个相对应的因子也要判断
{
int y2=it.first^(it.first/j);
if(y2>it.first && (it.first/j)==gcd(it.first,y2))
ans+=mp[it.first]*mp[y2];
}
}
}
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
H-井然有序之窗
题意
给定 n 个 [l,r] 范围,第 i 个元素取值范围为 [li, ri],问是否可以构造出满足每个元素都在其对应范围内的排列,有的话输出任意一个满足条件的排列,没有的话输出 -1
思路
先对这 n 个范围排序,按照左端点从小到大排;之后遍历排列 [1, n] 去分配每一个数,其中用优先队列来存储左端点小于当前 cur 的元素,就是要将目前可能可以满足条件的元素放到优先队列,优先队列依据 右端点从小到大排,看对头的右端点是否 >= cur;因为每个区间不能浪费,所以一旦有不满足 ( r>=cur ) 条件的队头,那就输出 -1;或者优先队列里面压根就没有区间了,但是排列还没有分配完,那也是输出 -1。
- 依据左端点从小到大排序
- 左端点小于等于 cur 的,加入到优先队列,优先队列按右端点从小到大
- 看优先队列队头的右端点是否大于等于 cur,如果是,记录答案,队头出队;如果不是,输出 -1
- 或者优先队列为空了,排列还没构造完,输出 -1
// https://ac.nowcoder.com/acm/contest/95323/H
// 1:依据左端点排序
// 2:左端点小于等于 cur 的,加入到优先队列,优先队列按右端点从小到大
// 3:看优先队列队头的右端点是否大于等于 cur,如果是,记录答案,队头出队;如果不是,输出 -1
// 3.1:或者优先队列为空了,排列还没构造完,输出 -1
#include <bits/stdc++.h>
using namespace std;
#define int long long
int t, n, m;
void solve()
{
cin >> n;
vector<array<int, 3>> a(n);
int index = 1;
for (auto &[l, r, i] : a)
{
cin >> l >> r;
i = index++;
}
sort(a.begin(), a.end());
set<array<int, 3>> que; // 有序表模拟优先队列
vector<int> ans(n + 1); // 这里要开大一点,因为下标取值为 [1,n]
for (int cur = 1, j = 0; cur <= n; cur++)
{
while (j < n && a[j][0] <= cur) // a[j][0] 表示数组的第 j 个元素的第 1 个子元素(就是矩阵的第 j 行第 1 列(j,0)
{
auto [l, r, i] = a[j];
que.insert({r, l, i});
j++;
}
if (que.empty())
{
cout << -1;
return;
}
auto [r, l, i] = *que.begin(); // 队头
que.erase(que.begin()); // 出队
if (r < cur)
{
cout << -1;
return;
}
ans[i] = cur;
}
for (int i = 1; i <= n; i++)
cout << ans[i] << " ";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
t = 1;
// cin >> t;
while (t--)
{
solve();
}
return 0;
}
M-数值膨胀之美
题意
必须进行一次且仅一次操作:选择一个非空区间 [l,r],l<=r,将区间里的所有元素都 *2,想办法使得数组中最大值与最小值的差,即 (max - min) 尽可能小。
前言
本人小白赛时混过去的,本篇思路是根据赛后增强测试点后做出的修改
思路
容易知道最小值肯定要进行操作;
延申最小值位置的左边以及右边,对于 a[i]*2 > max 的处理分两种情况:
-
遇到 a[i]*2 > max 的位置不操作了,直接退出延申
-
遇到 a[i]*2 > max 的位置继续延申,但是遇到 max 的位置退出延申
还有就是看是否需要将 [0, n],也就是数组里的所有数都进行操作,这个操作就是直接看结果 2*(max-mi),看是否会比以上处理得到的答案更优
这里给出几个比较强的测试点:
2
7 8
2
4
17 3 9 3
12
7
12 5 7 4 6 5 12
6
#include <bits/stdc++.h>
using namespace std;
#define int long long
int t, n, m;
int a[100005];//a数组用来进行情况1操作
int b[100005];//b数组用来进行情况2操作
void solve()
{
cin>>n;
int mi=1e9+7,mx=0,minindex=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
b[i]=a[i];
if(a[i]>mx)mx=a[i];
if(a[i]<mi)mi=a[i],minindex=i;
}
a[minindex]*=2,b[minindex]*=2;//最小值位置肯定要 *2
//情况1,如果 a[i]*2 > max,那就不操作了
for(int i=minindex+1;i<n;i++)
{
if(a[i]*2<=mx)a[i]*=2;
else break;
}
for(int i=minindex-1;i>=0;i--)
{
if(a[i]*2<=mx)a[i]*=2;
else break;
}
sort(a,a+n);
int ans=min(a[n-1]-a[0],2*(mx-mi));
//情况2,a[i]*2 > max,仍然操作,直到 a[i]=max 停止
for(int i=minindex+1;i<n;i++)
{
if(b[i]!=mx)b[i]*=2;
else break;
}
for(int i=minindex-1;i>=0;i--)
{
if(b[i]!=mx)b[i]*=2;
else break;
}
sort(b,b+n);
ans=min(ans,b[n-1]-b[0]);
cout<<ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
E-双生双宿之错
题意
定义一个数组长度为偶数,且数组只有两种数字,且这两种数字出现的次数相等,这样的数组称为双生数组。
给你一个数组,你一次操作可以对这个数组的任意位置的数 +1 或者 -1,问最少进行多少次操作,可以使得这个数组变成双生数组。
思路
-
看前半部分以及后半部分中位数,前半部分向第一个中位数靠,后半部分向第二个中位数靠。
-
需要注意的是两个中位数相等的情况,这时就要考虑前半部分中位数减1,或者后半部分中位数加1
取最优解
// https://ac.nowcoder.com/acm/contest/95323/E
// 看前半部分以及后半部分中位数,前半部分向第一个中位数靠,后半部分向第二个中位数靠
// 需要注意的是两个中位数相等的情况,这时就要考虑前半部分中位数减1,或者后半部分中位数加1
// 取最优解
#include <bits/stdc++.h>
using namespace std;
#define int long long
int t, n, m;
int a[100005];
void solve()
{
cin >> n;
map<int, int> mp;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
mp[a[i]]++;
}
if (mp.size() == 1)
{
cout << n / 2 << "\n";
return;
}
sort(a + 1, a + n + 1);
int mid1 = n / 2 - n / 4, mid2 = n - n / 4;
int ans = 0, ans1 = 0, ans2 = 0;
for (int i = 1; i <= n / 2; i++)
{
ans1 += abs(a[i] - a[mid1]);
}
for (int i = n / 2 + 1; i <= n; i++)
{
ans2 += abs(a[i] - a[mid2]);
}
ans = ans1 + ans2;
if (a[mid1] == a[mid2])
{
int cnt1 = 0, temp1 = a[mid1] - 1;
for (int i = 1; i <= n / 2; i++)
cnt1 += abs(a[i] - temp1);
int cnt2 = 0, temp2 = a[mid2] + 1;
for (int i = n / 2 + 1; i <= n; i++)
cnt2 += abs(a[i] - temp2);
// cout<<"cnt1 = "<<cnt1<<"\tcnt2 = "<<cnt2<<endl;
ans = min(ans1 + cnt2, ans2 + cnt1);
}
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
C-兢兢业业之移
题意
给定一个边长为 n 的方阵,将其中所有的 1 移动到左上区域,只能上下左右移动,先输出使用的总步数,每行再输出每一步中做交换的两个点,x1, y1, x2, y2,即 A: (x1, y2) 与 B: (x2, y2) 做了交换,其中 A 与 B 位置的输出顺序不做要求,且使用的步数不要求为最少步数。
思路
在方阵四分之一的左上区域中,如果 (a, b) 为 0,那么遍历所有的列,如果该列的下面或者上面有 1,且这个 1 不是从 (a, b) 的左上来的,那就交换;
已知终点 (c, d) 为 1,至于路径怎么走,分以下几种情况:
-
(c, d) 在 (a, b) 的右边,那么先竖着换,再横着换;
-
(c, d) 在 (a, b) 的左边,那么先横着换,再竖着换;
这是为了防止交换的路径干扰到原来已经填好的 1,举个例子看看就知道了(因为没有考虑到这个分类导致 WA 了一发)
#include <bits/stdc++.h>
using namespace std;
//#define int long long
int t, n, m;
// 记录路径
void ji(int a, int b, int c, int d, vector<array<int,4>>& ans)
{
if(d<b)// 终点在左下,需要先走横,再走竖
{
for(int j=d;j<b;j++)//先走横
ans.push_back({a+1,j+1+1,a+1,j+1});
for(int i=c;i>a;i--)//再走竖(这里 c 一定 大于 a)
ans.push_back({i-1+1,d+1,i+1,d+1});
}
else// 终点在右边,但是需要考虑 c 与 a 的大小
{
if(c>a)// c>a,那么竖着是递减地走
{
for(int i=c;i>a;i--)//行递减
ans.push_back({i-1+1,d+1,i+1,d+1});
}
if(c<a)// c<a,那么竖着是递增地走
{
for(int i=c;i<a;i++)//行递增
ans.push_back({i+1+1,d+1,i+1,d+1});
}
for(int j=d;j>b;j--)//横着走,列递减
ans.push_back({a+1,j-1+1,a+1,j+1});
}
}
// 寻找 1
void zhao(vector<string>& g, int x, int y, vector<array<int,4>>& ans)
{
for(int j=y;j<n;j++){
for(int i=x;i<n;i++){// j < n/2 的时候不能往上找,因为上面的 1 是已经填好了的,别动了
if(g[i][j]=='1'){
ji(x,y,i,j,ans);
g[x][y]='1',g[i][j]='0';
return;
}
}
if(j>=n/2){// j >= n/2 的时候才可以往上找 1
for(int i=x+1;i>=0;i--){
if(g[i][j]=='1'){
ji(x,y,i,j,ans);
g[x][y]='1',g[i][j]='0';
return;
}
}
}
}
//如果遍历完了右边的列还没有找到 1,那就遍历左边的列
for(int j=0;j<y;j++)
{
for(int i=x+1;i<n;i++)
{
if(g[i][j]=='1')
{
ji(x,y,i,j,ans);
g[x][y]='1',g[i][j]='0';
return;
}
}
//左边的列一定不能往上找,因为不能动已经填好的 1,不信举个例子试试
}
}
void solve()
{
cin>>n;
vector<string> g(n);
for(int i=0;i<n;i++)cin>>g[i];
vector<array<int,4>> ans;
for(int i=0;i<n/2;i++)
{
for(int j=0;j<n/2;j++)
{
if(g[i][j]=='0')
{
zhao(g,i,j,ans);
}
}
}
cout<<ans.size()<<"\n";
for(auto [a,b,c,d]:ans)
{
cout<<a<<" "<<b<<" "<<c<<" "<<d<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现