2025牛客寒假算法基础集训营1题解(ABDEGHJM)

牛客集训营算法题解精讲

比赛链接:2025牛客寒假算法基础集训营1

 

A-茕茕孑立之影

思路:由于 1 是任何数的因数,所以如果数组中有 1 的话直接输出 -1 即可;如果没有 1 ,我们知道 ai 不可能是质数的因数,所以我们肯定要找一个质数,然后又要使得这个质数不能是 ai 的因数,那么所找的质数 x 需要比所有 ai 都大。时间复杂度 O(n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

int t;

bool isp(int n)//判断质数
{
    if (n <= 1)
        return 0;
    if (n == 2 || n == 3)
        return 1; 
    if (n % 2 == 0 || n % 3 == 0)
        return 0;
    int sqn = sqrt(n);
    for (int i = 5; i <= sqn; i += 6)
    { 
        if (n % i == 0 || n % (i + 2) == 0)
            return 0;
    }
    return 1;
}

void solve()
{
    int n;
    cin >> n;
    bool flag = 0;
    int maxn = 0;
    rep(i, 1, n)
    {
        int a;
        cin >> a;
        maxn = max(a, maxn);
        if(a==1)
            flag = 1;
    }
    if(flag)
    {
        cout << -1 << endl;
        return;
    }
    int i = maxn + 1;
    while(!isp(i))
        i++;
    cout << i << endl;
}

int main()
{
    IOS;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

 

B-一气贯通之刃

思路:因为在一棵树中任意两点间有且只有一条简单路径,如果存在一个点的度数大于 2 那么一条路径肯定会有走不到的边。所以,在一棵树中,如果存在一条简单路径,使得这条路径不重不漏地经过所有结点,那么这棵树一定是一条链。一条链有且仅有 2 个度数为 1 的端点,剩下的度数都为 2 ,根据这个特点判断这棵树是否是链,然后输出端点即可。时间复杂度 O(n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

vector<int> d;//度数

int main()
{
    IOS;
    int n;
    cin >> n;
    d.resize(n + 1);
    rep(i,1,n-1)
    {
        int u, v;
        cin >> u >> v;
        d[u]++;
        d[v]++;
    }
    int cnt = 0;//度数为 1 的点的数量
    int p1 = 0, p2 = 0;
    rep(i,1,n)
    {
        if (d[i] == 1)
        {
            cnt++;
            if (cnt == 1)
                p1 = i;
            else if(cnt == 2)
                p2 = i;
        }
        else if (d[i] != 2)
        {
            cout << -1 << endl;
            return 0;
        }
    }
    if (cnt != 2)
        cout << -1 << endl;
    else
        cout << p1 << " " << p2 << endl;
    return 0;
}

 

D-双生双宿之决

思路:模拟即可。时间复杂度 O(n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

int t;

void solve()
{
    int n;
    cin >> n;
    unordered_map<int,int>vs;
    int fro;
    rep(i,1,n)
    {
        int a;
        cin >> a;
        vs[a]++;
        if(i==1)
            fro=a;
    }
    if(n%2!=0||vs.size()!=2)
    {
        cout << "No" << endl;
        return;
    }
    for(auto &u:vs)
    {
        if(u.second!=vs[fro])
        {
            cout << "No" << endl;
            return;
        }
        else
        {
            cout << "Yes" << endl;
            return;
        }
    }
}

int main()
{
    IOS;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

 

E-双生双宿之错

思路:我们发现,如果每次操作都对一个数组的某个元素加 1 或者减 1 ,直到数组的所有元素都变得相同,那么最小的操作次数就是将所有元素都变成数组的中位数。于是我们可以将数组排序,分成前后两个半段,然后求出前后两个半段的中位数,根据以上结论求出最小操作次数即可。需要注意的是,如果前后两段的中位数相等,那么我们可以分别求出前段的中位数减 1 后,和后段的中位数加 1 后的操作次数,比较得出最小的结果。时间复杂度 O(n log n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

vector<LL>a;

LL cnt(LL m1, LL m2, int n)//计算操作次数
{
    LL res=0;
    rep(i,1,n/2)
        res+=abs(a[i]-m1);
    rep(i,n/2+1,n)
        res+=abs(a[i]-m2);
    return res;
}

int main()
{
    IOS;
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cin >> n;
        a.resize(n+1);
        rep(i,1,n)
            cin >> a[i];
        sort(a.begin()+1,a.begin()+n+1);
        LL m1,m2;//前后半段的中位数
        if((n/2)%2==0)
            m1=(a[(1+n/2)/2]+a[(1+n/2)/2+1])/2,m2=(a[(n/2+1+n)/2]+a[(n/2+1+n)/2+1])/2;
        else
            m1=a[(1+n/2)/2],m2=a[(n/2+1+n)/2];
        if(m1==m2)
        {
            m1--;
            LL ans1=cnt(m1,m2,n);
            m1++,m2++;
            LL ans2=cnt(m1,m2,n);
            cout << min(ans1,ans2) << endl;
        }
        else
            cout << cnt(m1,m2,n) << endl;
    }
    return 0;
}

 

G-井然有序之衡

思路:我们发现题意的操作对数组和的贡献其实是 0 ,所以只要数组和等于 n 的排列和就可以变成排列。由于需要求的是最小操作次数,我们贪心地思考,把数组中最小的数变成 1 ,倒数第二的变成 2 ......以此类推。我们可以将数组排序,求每个数与目标数的差距,发现由于操作后数组和不会变,那么总的正差距一定等于总的负差距,所以我们每次操作都要缩小一个正差距和负差距,输出正差距即为最小操作次数。时间复杂度 O(n log n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

LL a[100005]; 

int main()
{
    IOS;
    int n;
    cin >> n;
    LL sum = 0;
    rep(i,1,n)
    {
        cin >> a[i];
        sum += a[i];
    }
    if(sum!=((LL)(1+n)*(LL)n)/2)//数组和 sum 必须等于 n 的排列和
    {
        cout << -1;
        return 0;
    }
    sort(a + 1, a + 1 + n);
    LL po = 0;
    rep(i,1,n)
    {
        LL d = a[i] - i;
        if(d>=0)
            po += d;
    }
    cout << po;
    return 0;
}

 

H-井然有序之窗

思路:使用贪心策略。从 1 到 n 按顺序选择区间指定 i 时,我们发现,在包括 i 的前提下,我们应该尽可能选择右端点较小的区间,如右端点相等则选择左端点更小的区间(即更靠左的区间),由于 i 是在递增的,这样的区间紧迫性更强,优先处理尽量避免后续区间无法找到合适的数字,如果找不到合适的区间则输出 -1 。具体地,我们使用优先队列来维护右端点最小的区间,每次只将左端点等于 i 的区间放入优先队列,然后将 i 指定给最合适的区间并弹出。时间复杂度 O(n log n) 可以通过此题。

#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

vector<vector<pair<int, int> > >qu;
priority_queue<pair<int, int>, vector<pair<int, int> >,greater<pair<int, int> > >pq;
vector<int>ans;

int main()
{
    IOS;
    int n;
    cin >> n;
    qu.resize(n);
    rep(i,1,n)
    {
        int l,r;
        cin >> l >> r;
        qu[l].push_back({r,i});
    }
    ans.resize(n);
    rep(i,1,n)
    {
        for(auto &u:qu[i])
            pq.push(u);
        if(pq.empty()||pq.top().first<i)
        {
            cout << -1;
            return 0;
        }
        ans[pq.top().second-1]=i;
        pq.pop();
    }
    for(auto &u:ans)
        cout << u << " ";
    return 0;
}

 

J-硝基甲苯之袭

思路:我们设所取的两个数为 x 和 y,令 p 等于 x^y,根据异或运算的性质,x^p 就会等于 y ,于是我们只需判断 p == gcd(x,  x^p) 就能知道 x 与 y 是否为所求,又两个数的 gcd 肯定是其中之一的因数,所以我们只需枚举数组每个数的因数,判断组合是否为所求,然后记录组合个数即可。时间复杂度 O(n sqrt(n) log n) 可以通过此题。

#include <bits/stdc++.h>
#include <numeric>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

int a[200005];
unordered_map<int, int> vs;
map<pair<int,int>, int >mp;

int gcd(int a, int b)
{
    while (b != 0) 
    {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

int main()
{
    IOS;
    int n;
    cin >> n;
    rep(i, 1, n)
    {
        cin >> a[i];
        vs[a[i]]++;
    }
    LL cnt = 0;
    rep(i, 1, n)
    {
        int sqn = sqrt(a[i]);
        rep(j, 1, sqn)
        {
            if (a[i] % j == 0)
            {
                int p1 = j, p2 = a[i] / j;
                int y1 = a[i] ^ p1, y2 = a[i] ^ p2;
                if (p1 == gcd(a[i], y1) && vs[y1] != 0 && y1 != a[i] && mp[{a[i], y1}] == 0 && mp[{y1, a[i]}] == 0)
                {
                    cnt+=vs[a[i]]*vs[y1];
                    mp[{a[i], y1}] = 1, mp[{y1, a[i]}] = 1;
                }
                if (p2 == gcd(a[i], y2) && vs[y2] != 0 && y2 != a[i] && mp[{a[i], y2}] == 0 && mp[{y2, a[i]}] == 0)
                {
                    cnt+=vs[a[i]]*vs[y2];
                    mp[{a[i], y2}] = 1, mp[{y2, a[i]}] = 1;
                }
            }
        }
    }
    cout << cnt;
    return 0;
}

 

M-数值膨胀之美

思路:贪心的思想,由于极差由最小值与最大值决定,所以我们尽量翻倍较小值。可以维护一个覆盖前 i 小值的区间 [l, r](区间初始只覆盖最小值,逐渐扩展覆盖到其他值),在区间扩展到覆盖第 i 小值的过程中记录数组最大值和最小值变化,从而计算出最小的极差。具体地,我们可以通过将数组 a 排序成 sa,来轻易地得出前 i 小值,每次扩展后的数组最小值不是第一小值 sa[1] 的两倍,就是下一个要拓展的值 sa[i+1] ,最大值是区间内每个数乘 2 后的最大值。时间复杂度 O(n log n) 可以通过此题。

#include <bits/stdc++.h>
#include <numeric>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
using namespace std;

LL a[100005];
vector<pair<int,int>>sa;

int main()
{
    IOS;
    int n;
    cin >> n;
    sa.resize(n+2);
    rep(i,1,n)
    {
        cin >> a[i];
        sa[i].first=a[i],sa[i].second=i;
    }
    sa[n+1].first=2e9;
    sort(sa.begin()+1,sa.begin()+1+n);
    int l=sa[1].second,r=sa[1].second;
    LL maxn=max(sa[1].first*2,sa[n].first);
    LL ans=maxn-min(sa[1].first*2,sa[2].first);
    rep(i,2,n)
    {
        while(sa[i].second<l)
        {
            l--;
            maxn=max(maxn,a[l]*2);
        }
        while(sa[i].second>r)
        {
            r++;
            maxn=max(maxn,a[r]*2);
        }
        ans=min(ans,maxn-min(sa[1].first*2,sa[i+1].first));
    }
    cout << ans;
    return 0;
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值