2025牛客寒假算法基础集训营1

寒假第一场牛客算法训练营。

过题数 5/13,补题数 10/13
赛时简单难度全出,中等难度出一题。

第二道中等难度题是道原题,不过没有搞清楚排序的规律,导致WA8发也没有做出来。

第一道中等难度的题能做出来还是比较欣喜的,因为我赛前复习了 multiset 的用法,多看题是好的,希望自己不要废弃掉这个习惯。

A.茕茕孑立之影

给你一个数组,要求找到一个不大于 10^18 的正整数 x,满足 x 和数组中任意一个元素都是互不为倍数的关系,所有的数都不是 x 的倍数,x 也不是任何数的倍数。

如果数组中有 1 的话,那么所有的数都是 1 的倍数,没有答案输出 -1,否则,直接输出一个很大的质数就好。a[i]<=1e9,我们输出1e9+7就好。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second

void miaojiachun()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    int flag = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (a[i] == 1)
        {
            flag = 1;
        }
    }

    if (flag == 1)
    {
        cout << "-1" << endl;
        return;
    }
    cout<<mod<<endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

B.一气贯通之刃

小红拿到了一棵树,她想请你寻找一条简单路径,使得这条路径不重不漏的经过所有节点。如果不存在这样的简单路径,则直接输出 −1。

简单路径是指这样一条路径,其经过的顶点和边互不相同。

简单路径不能经过一个点两次,因此如果一个点有 3 条或者更多条边,就一定会有走不到的边。

因此这棵树一定会是一条链,那么我们只需要找到链的两个端点即可。
我们只要计算每个点的度数,答案就是度数为 1 的两个点(并且度数为 1 的点有且只有两个)。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
    int n;
    cin >> n;

    map<int, vector<int>> g;
    vector<int> de(n + 1, 0);

    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
        de[u]++;
        de[v]++;
    }
    vector<int> node;
    for (int i = 1; i <= n; ++i) {
        if (de[i] == 1) {
            node.push_back(i);
        }
    }
    if (node.size() != 2) {
        cout << -1 << endl; 
    } else {
        cout << node[0] << " " << node[1] << endl; 
    }
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    // cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

D.双生双宿之决

判断一个数组长度是否为偶数,是否只含两种元素,且这两种元素的出现次数是否相同。

模拟即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍

void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    map<int,int>u;
    vector<int>b;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(u[a[i]]==0){
            b.push_back(a[i]);
        }
        u[a[i]]++;
    }
    if(b.size()==2 and u[b[0]]==u[b[1]] and n%2==0){
        cout<<"Yes"<<endl;
    }else{
        cout<<"No"<<endl;
    }

}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

G.井然有序之衡

小红拿到了一个数组,她可以进行任意次以下操作:选择两个元素,使得其中一个加 1,另一个减 1。
小红希望最终数组变成一个排列,请你帮助他确定这能否实现。如果可以实现的话,还需要求出最小操作次数。

首先进行排序,如果排序之后就是一个排列,那么就直接输出 0 就好了。否则我们记录每个数组要加多少或减多少。如果加和减的大小不同,我们输出 -1,因为不可能实现。否则,输出要加的数的大小,即为次数

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    vector<int>b(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        b[i]=i;
    }
    sort(a.begin()+1,a.end());
    int kk=0;
    for(int i=1;i<=n;i++){
        if(a[i]!=b[i]){
            kk=1;
        }
    }
    if(kk==0){
        cout<<"0"<<endl;
        return;
    }
    int sum1=0,sum2=0;
    for(int i=1;i<=n;i++){
        if(a[i]<b[i]){
            sum1+=b[i]-a[i];
        }if(a[i]>b[i]){
            sum2+=a[i]-b[i];
        }
    }
    if(sum1!=sum2){
        cout<<"-1"<<endl;
        return;
    }
    cout<<sum1<<endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    // cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

M.数值膨胀之美

定义一个数组的极差:数组的元素最大值减去最小值。

一个数组,进行恰好一次操作:选择一个非空区间,将其中所有元素都乘以 2。请最小化数组的极差。

multiset的插入和删除都是log级的操作。所以我们用这个来对数组状态进行更新不会超时。

首先如果数组所有元素都相等我们输出 0 就好。

否则我们要做的操作是:使用两个数组,一个进行更改,一个进行记录。首先最大值是不能变的,我们要做的是找到数组中的最小值,以这个最小值的下标为基准,向左右两边进行范围的扩大,将范围内的数扩大为原来的两倍,每次更改都更新一遍现有极差,如果极差增大了我们就进行还原操作。我们先用multiset进行数组的全部存入。因为他是自动按有序存储,且可以储存重复元素,所以我们可以在这里找到数组中的所有元素。

因为一定要进行更改,所以最小值一定会扩大为原来的两倍。我们插入新值,当前的极差就是初始极差,之后利用指针l,r,以最小值下标为基准进行遍历,不断更新极差。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    vector<int>b(n+1);
    multiset<int>c;
    int max1=0,min1=inf/2;
    int k=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        b[i]=a[i];
        c.insert(a[i]);
        max1=max(max1,a[i]);
        if(min1>a[i]){
            min1=a[i];
            k=i;
        }
    }
    int flag=0;
    for(int i=1;i<n;i++){
        if(a[i]!=a[i+1]){
            flag=1;
        }
    }
    if(flag==0){
        cout<<"0"<<endl;
        return;
    }
    c.erase(c.find(b[k]));
    b[k]*=2;
    c.insert(b[k]);
    int ans=*c.rbegin()-*c.begin();    
    int l=k-1,r=k+1;
    while(1){
        int flag1=0,flag2=0;
        if(l>=1 and a[l]!=*c.rbegin()){
            c.erase(c.find(a[l]));
            c.insert(a[l]*2);
            int ans1=*c.rbegin()-*c.begin();
            if(ans1<ans){
                flag1=1;
                ans=ans1;
                l--;
            }else{
                c.erase(c.find(a[l]*2));
                c.insert(a[l]);
            }
        }if(r<=n and a[r]!=*c.rbegin()){
            c.erase(c.find(a[r]));
            c.insert(a[r]*2);
            int ans1=*c.rbegin()-*c.begin();
            if(ans1<ans){
                flag2=1;
                ans=ans1;
                r++;
            }else{
                c.erase(c.find(a[r]*2));
                c.insert(a[r]);
            }
        }
        if(flag1==0 or flag2==0){
            break;
        }
    }
    cout<<ans<<endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    // cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

H.井然有序之窗

一个比较经典的贪心,
i 这个数必须在所有 l≤i,r≥i 的区间中选到,如果有多种选择,选择 r 最小的区间一定不会使得答案变劣。

一个例子:有两个区间 [1,1],[1,2] ,我们 1 如果在第二个区间中选,会导致 2 这个数无法选到。

我们可以使用优先队列维护右端点最小的区间,对区间按左端点排序后,可以从前往后将左端点小于 i 的区间加入优先队列,然后取出右端点最小的区间。

若右端点小于 i ,理论上应该将这个区间丢弃,找到第一个满足右端点大于等于 i 的区间,但由于区间不能浪费,因此此时一定无解。若优先队列为空,同样无解。

时间复杂度 O(nlogn) 。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
struct pp{
    int l,r,k;
};
void miaojiachun()
{
    int n;
    cin>>n;
    vector<pp>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i].l>>a[i].r;
        a[i].k=i;
    }
    sort(a.begin()+1,a.end(),[&](const pp &a,const pp &b)->bool{
        if(a.l!=b.l) return a.l<b.l;
        return a.r<b.r;
    });
    priority_queue<PII,vector<PII>,greater<PII>>pq;
    vector<int>res(n+1);
    int pos=1;
    for(int i=1;i<=n;i++){
        while(pos<=n and a[pos].l<=i){
            pq.emplace(a[pos].r,a[pos].k);
            pos++;
        }
        while(!pq.empty() and pq.top().x<i){
            pq.pop();
        }
        if(pq.empty()){
            cout<<"-1"<<endl;
            return;
        }
        auto[r,idx]=pq.top();
        pq.pop();
        res[idx]=i;
    }
    for(int i=1;i<=n;i++){
        cout<<res[i]<<" ";
    }
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    // cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

J.硝基甲苯之袭

打表发现 x,y(x<y) 满足 x⊕y=gcd(x,y) ,当且仅当 y=x+gcd(x,y) ,且 x 是偶数。

我们枚举每个偶数 x ,和 x 的因子 t 得到 y=x+t ,判断上述式子是否合法,若合法统计答案即可。

枚举 x 和 x 的因子 t ,那么 y=x⊕t ,再检查 gcd(x,y)=t 是否合法,若合法统计答案。时间复杂度 O(n*sqrt(n)).

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍

void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(2*n+1);
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        a[x]++;
    }
    int ans=0;
    for(int i=2;i<=n;i+=2){
        for(int j=1;j*j<=i;j++){
            if(i%j) continue;
            int x=i,y=j;
            y+=x;
            if((x^y)==gcd(x,y)) ans+=1ll*a[x]*a[y];
            if(j*j!=i){
                y=i/j;
                y+=x;
                if((x^y)==gcd(x,y)) ans+=1ll*a[x]*a[y];
            }
        }
    }
    cout<<ans<<endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    // cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

E.双生双宿之错

假设我们确定了双生数组的两种元素分别为 ( x ) 和 ( y ),且 ( x < y )。

如果数组是有序的,将前一半变成 ( x ),后一半变成 ( y ),操作次数为前一半元素与 ( x ) 的差值的绝对值之和,加上后一半元素与 ( y ) 的差值的绝对值之和。

将数组分成左右两部分,分别求值。问题转化为:将所有数变成某个值 ( t ) 的最小操作次数。

一个显然的结论是:取 ( t ) 为这个数组的中位数。

感性理解:如果所有数字都变成了 ( t ),现在要使得所有数字变成 ( t + 1 ),需要让原本小于等于 ( t ) 的数字都加一次(记个数为 ( l )),原本大于 ( t ) 的数字少加一次(记个数为 ( r )),代价是 ( l - r ),其中 ( l + r = n )。当 ( t ) 为中位数时,( l ) 会大于等于 ( r ),代价就变成了非负整数。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍

void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a.begin()+1,a.end());
    auto get=[&](auto x,auto y){
        auto ans=0ll;
        for(int i=1;i<=n/2;i++){
            ans+=abs(a[i]-x);
        }
        for(int i=n/2+1;i<=n;i++){
            ans+=abs(a[i]-y);
        }
        return ans;
    };
    auto check=[&](int x,int y){
        auto t=0ll;
        if(a[x]!=a[y]) t=get(a[x],a[y]);
        else t = min(get(a[x] - 1, a[y]), get(a[x], a[y]+1));
        return t;
    };
    int t=n>>1;
    int x=0,y=0;
    if(t & 1){
        x=(t+1)>>1;
        y=t+x;
    }else{
        x=t>>1;
        y=t+x+1;
    }
    cout<<check(x,y)<<endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

C.兢兢业业之移

我们需要把所有的 1 都推到左上角。可以先将所有的 1 往最上方推,再将所有的 1 往最左边推,此时所有的 1 会形成一个类似上三角矩阵的阶梯。

接下来,我们需要将右边多出的 1 和下边多出的 1 放进左上角。从边缘的 1 开始,逐步向中间靠拢即可。

操作次数大致如下:

  1. 每个 1 从最底下移动到最上方,需要 ( 2n ) 次操作。
  2. 每个 1 从最右边移动到最左边,也需要 ( 2n ) 次操作。
  3. 从边角往中间靠拢,还需要 ( 2n ) 次操作。

总操作次数为 ( 4n )。假设 1 的个数为 ( n^2 / 4 ),则总操作次数为 ( n^3 )。

理论上,操作次数超过了题目的要求,但实际上并非每个 1 都需要走满 ( 2n ) 步。例如,如果一个 1 从右下角走到左上角走了 ( 2n ) 步,必然会使得其他 1 不需要走满 ( 2n ) 步。因此,每个 1 的移动步数并不是独立的,实际操作次数不会超过题目的要求。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
char c[105][105];
struct xx{
    int a,b,c,d;
};
void miaojiachun()
{
    int n;
    cin>>n;
    vector s(n+2,string(n+2,'1'));
    for(int i=1;i<=n;i++){
        cin>>s[i];
        s[i]='1'+s[i]+'1';
    }
    vector<vector<int>>ans;
    auto go=[&](int x,int y,char c){
        int dx=0;
        dx-=c=='U';
        dx+=c=='D';
        dx+=x;
        int dy=0;
        dy-=c=='L';
        dy+=c=='R';
        dy+=y;
        swap(s[x][y],s[dx][dy]);
        ans.push_back({x,y,dx,dy});
    };
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=i;k>=1;k--){
                if(s[k][j]=='1' and s[k-1][j]=='0'){
                    go(k,j,'U');
                }else{
                    break;
                }
            }
        }
    }for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=j;k>=1;k--){
                if(s[i][k]=='1' and s[i][k-1]=='0'){
                    go(i,k,'L');
                }else{
                    break;
                }
            }
        }
    }
    for(int i=n/2;i>=1;i--){
        for(int j=n;j>n/2;j--){
            if(s[i][j]=='0'){
                continue;
            }
            int x=0,y=0;
            for(int k=1;k<=n/2;k++){
                for(int l=1;l<=n/2;l++){
                    if(s[k][l]=='1') continue;
                    x=k;
                    y=l;
                    break;
                }
            }
            for(int k=i;k<x;k++){
                go(k,j,'D');
            }
            for(int k=j;k>y;k--){
                go(x,k,'L');
            }
        }
    }
    for(int i=n;i>n/2;i--){
        for(int j=n/2;j>=1;j--){
            if(s[i][j]=='0') continue;
            int x=0,y=0;
            for(int k=1;k<=n/2;k++){
                for(int l=1;l<=n/2;l++){
                    if(s[k][l]=='1') continue;
                    x=k;
                    y=l;
                    break;
                }
            }
            for(int k=j;k<y;k++){
                go(i,k,'R');
            }
            for(int k=i;k>x;k--){
                go(k,y,'U');
            }
        }
    }
    cout<<ans.size()<<endl;
    for(auto& i:ans){
        for(auto&j:i){
            cout<<j<<" ";
        }
        cout<<endl;
    }
}   
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

F.双生双宿之探

首先要观察双生数组的性质。可以发现,可能存在双生数组的区间一定是一段只包含两种数字的区间。假设区间中的两种数字分别是 x 和 y ,我们称这种区间为“ xy -好区间”。

“ xy -好区间”应该是极长的。如果一个较长的“ xy -好区间”包含了另一个较短的“ xy -好区间”,我们需要舍去较短的那个,而在较长的区间中进行计算。(具体原因暂不讨论)

将所有极长的“ xy -好区间”排序后会发现,连续的 3 个好区间中,第 1 个和第 3 个一定是不相交的。例如,假设第一个区间是 x 和 y ,第二个区间是 y 和 z ,那么第三个区间如果与第一个区间相交,就必然包含 x 、 y 、 z 三种数字。因此,这些极长的“ xy -好区间”的总长度不超过 2n 。

一个“ xy -好区间”中有多少个双生数组呢?

假设一个数组只包含 1 和 -1,那么双生数组的定义就等价于区间和为 0 的区间。

如何解决这个问题呢?

我们记数组的前缀和为 f ,那么 f_r - f_l = 0 当且仅当 f_r = f_l 。因此,可以用一个容器记录 f_r 的出现次数,从而计算以 r 为结尾的“好区间”有多少个。

接下来,我们将“ xy -好区间”中等于 x 的数变成 1,等于 y 的数变成 -1,就可以用上述方法计算双生数组的个数了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
void miaojiachun()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    vector<int>f(n+1);
    auto ans=0ll;
    for(int i=1;i<=n;i++){
        set<int>st;
        map<int,int>mp;
        mp[0]++;
        f[i-1]=0;
        for(int j=i;j<=n;j++){
            st.insert(a[j]);
            if(st.size()>2){
                i=j;
                st.clear();
                for(;i>=1;i--){
                    st.insert(a[i]);
                    if(st.size()>2){
                        break;
                    }
                }
                break;
            }
            f[j]=f[j-1];
            if(a[j]==a[i]) f[j]++;
            else{
                f[j]--;
            }
            ans+=mp[f[j]];
            mp[f[j]]++;
            if(j==n) i=n;
        }
    }
    cout<<ans<<endl;
}   
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ING = 1;
    cin >> ING;
    while (ING--)
    {
        miaojiachun();
    }
    return 0;
}

that's all.

posted @ 2025-01-26 10:30  miao-jc  阅读(47)  评论(0)    收藏  举报