2025牛客寒假算法基础集训营第一场补题

目录

  1. J-硝基甲苯之袭
  2. H-井然有序之窗
  3. M-数值膨胀之美
  4. E-双生双宿之错
  5. C-兢兢业业之移

J-硝基甲苯之袭

原题: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-井然有序之窗

原题:H-井然有序之窗

题意

给定 n 个 [l,r] 范围,第 i 个元素取值范围为 [li, ri],问是否可以构造出满足每个元素都在其对应范围内的排列,有的话输出任意一个满足条件的排列,没有的话输出 -1

思路

先对这 n 个范围排序,按照左端点从小到大排;之后遍历排列 [1, n] 去分配每一个数,其中用优先队列来存储左端点小于当前 cur 的元素,就是要将目前可能可以满足条件的元素放到优先队列,优先队列依据 右端点从小到大排,看对头的右端点是否 >= cur;因为每个区间不能浪费,所以一旦有不满足 ( r>=cur ) 条件的队头,那就输出 -1;或者优先队列里面压根就没有区间了,但是排列还没有分配完,那也是输出 -1。

  1. 依据左端点从小到大排序
  2. 左端点小于等于 cur 的,加入到优先队列,优先队列按右端点从小到大
  3. 看优先队列队头的右端点是否大于等于 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-数值膨胀之美

原题:M-数值膨胀之美

题意

必须进行一次且仅一次操作:选择一个非空区间 [l,r],l<=r,将区间里的所有元素都 *2,想办法使得数组中最大值与最小值的差,即 (max - min) 尽可能小。

前言

本人小白赛时混过去的,本篇思路是根据赛后增强测试点后做出的修改

思路

容易知道最小值肯定要进行操作;

延申最小值位置的左边以及右边,对于 a[i]*2 > max 的处理分两种情况:

  1. 遇到 a[i]*2 > max 的位置不操作了,直接退出延申

  2. 遇到 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-双生双宿之错

原题: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-兢兢业业之移

原题: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;
}

posted @   明天天晴KKK  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
点击右上角即可分享
微信分享提示